From f61d0d22213925a958144d2f567697f04bad9164 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Wed, 25 Sep 2024 16:32:27 -0400 Subject: [PATCH 1/7] done --- qiskit_ibm_runtime/utils/embeddings.py | 450 ++++++------------------- test/unit/test_embeddings.py | 432 +++++++++++++++++++++++- 2 files changed, 521 insertions(+), 361 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index 8f5ad9a61..f5a6196c9 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -15,7 +15,8 @@ Utility class to represent an embedding of a set of qubits in a two-dimensional plane. """ -from typing import List, Tuple, Union +from dataclasses import dataclass +from typing import List, Tuple, Union, Sequence from qiskit.providers.backend import BackendV2 from qiskit.transpiler import CouplingMap @@ -61,8 +62,12 @@ def from_backend(cls, backend: BackendV2) -> "Embedding": """ if not (coupling_map := backend.coupling_map): raise ValueError(f"Coupling map for backend '{backend.name}' is unknown.") - if not (coordinates := _get_qubits_coordinates(backend.num_qubits)): - raise ValueError(f"Coordinates for backend '{backend.name}' are unknown.") + try: + coordinates = _get_qubits_coordinates(backend.num_qubits) + except ValueError as err: + raise ValueError( + f"Failed to fetch coordinates for backend '{backend.name}' with error: {err}" + ) return cls(coordinates, coupling_map) @@ -81,6 +86,54 @@ def coupling_map(self) -> CouplingMap: return self._coupling_map +@dataclass(frozen=True) +class _Row: + r""" + A class representing a dense row of the standard heavy hex planar embedding. + + Args: + num_qubits: The number of qubits in the dense row. + x_offset: The integer x-offset of the row. + down_links: The positions of dense row, wrt the first qubit, at which to place qubits below. + """ + + num_qubits: int + x_offset: int = 0 + down_links: Sequence[int] = () + + +def _heavy_hex_coords(rows: Sequence[_Row], row_major: bool = True) -> list[tuple[int, int]]: + """Generate heavy hex coordinates for the given rows. + + Args: + rows: The dense rows specifications. + row_major: Whether qubits should be labelled in row-major order (by ``x`` first and, in + case of a tie, by ``y``) or in colum-major order. + + Returns: + A list of qubit coordinates, where list position corresponds with qubit index. + """ + coordinates = [] + + # Add coordinates in row-major order + row_idx = 0 + for row in rows: + if row.num_qubits: + for col_idx in range(row.num_qubits): + coordinates += [(row_idx, col_idx + row.x_offset)] + row_idx += 1 + if row.down_links: + for col_idx in row.down_links: + coordinates += [(row_idx, col_idx + row.x_offset)] + row_idx += 1 + + # Sort if colum-major order is required + if not row_major: + coordinates = sorted(coordinates, key=lambda p: (p[1], p[0])) + + return coordinates + + def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: r""" Return a list of coordinates for drawing a set of qubits on a two-dimensional plane. @@ -91,12 +144,19 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: Args: num_qubits: The number of qubits to return the coordinates from. + + Returns: + A list of coordinates for drawing a set of qubits on a two-dimensional plane. + + Raises: + ValueError: If the coordinates for a backend with ``num_qubit`` qubits are unknown. """ if num_qubits == 5: return [(1, 0), (0, 1), (1, 1), (1, 2), (2, 1)] if num_qubits == 7: - return [(0, 0), (0, 1), (0, 2), (1, 1), (2, 0), (2, 1), (2, 2)] + rows = [_Row(3, 0, (1,)), _Row(3, 0)] + return _heavy_hex_coords(rows) if num_qubits == 15: return [ @@ -117,368 +177,50 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: (1, 0), ] - if num_qubits == 20: - return [ - (0, 0), - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (1, 0), - (1, 1), - (1, 2), - (1, 3), - (1, 4), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (3, 0), - (3, 1), - (3, 2), - (3, 3), - (3, 4), - ] - if num_qubits == 16: - return [ - (1, 0), - (1, 1), - (2, 1), - (3, 1), - (1, 2), - (3, 2), - (0, 3), - (1, 3), - (3, 3), - (4, 3), - (1, 4), - (3, 4), - (1, 5), - (2, 5), - (3, 5), - (1, 6), - ] + rows = [_Row(0, 0, (3,)), _Row(7, 0, (1, 5)), _Row(5, 1, (2,))] + return _heavy_hex_coords(rows, False) + + if num_qubits == 20: + rows = [row := _Row(5, 0), row, row, row] + return _heavy_hex_coords(rows) if num_qubits == 27: - return [ - (1, 0), - (1, 1), - (2, 1), - (3, 1), - (1, 2), - (3, 2), - (0, 3), - (1, 3), - (3, 3), - (4, 3), - (1, 4), - (3, 4), - (1, 5), - (2, 5), - (3, 5), - (1, 6), - (3, 6), - (0, 7), - (1, 7), - (3, 7), - (4, 7), - (1, 8), - (3, 8), - (1, 9), - (2, 9), - (3, 9), - (3, 10), - ] + rows = [_Row(0, 0, [3, 7]), _Row(10, 0, [1, 5, 9]), _Row(10, 1, [2, 6])] + return _heavy_hex_coords(rows, False) if num_qubits == 28: - return [ - (0, 2), - (0, 3), - (0, 4), - (0, 5), - (0, 6), - (1, 2), - (1, 6), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (2, 5), - (2, 6), - (2, 7), - (2, 8), - (3, 0), - (3, 4), - (3, 8), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - (4, 5), - (4, 6), - (4, 7), - (4, 8), + rows = [ + _Row(5, 2, (0, 4)), + _Row(9, 0, (0, 4, 8)), + _Row(9, 0), ] + return _heavy_hex_coords(rows=rows) if num_qubits == 53: - return [ - (0, 2), - (0, 3), - (0, 4), - (0, 5), - (0, 6), - (1, 2), - (1, 6), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (2, 5), - (2, 6), - (2, 7), - (2, 8), - (3, 0), - (3, 4), - (3, 8), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - (4, 5), - (4, 6), - (4, 7), - (4, 8), - (5, 2), - (5, 6), - (6, 0), - (6, 1), - (6, 2), - (6, 3), - (6, 4), - (6, 5), - (6, 6), - (6, 7), - (6, 8), - (7, 0), - (7, 4), - (7, 8), - (8, 0), - (8, 1), - (8, 2), - (8, 3), - (8, 4), - (8, 5), - (8, 6), - (8, 7), - (8, 8), - (9, 2), - (9, 6), + rows = [ + _Row(5, 2, (0, 4)), + r1 := _Row(9, 0, (0, 4, 8)), + r2 := _Row(9, 0, (2, 6)), + r1, + r2, ] + return _heavy_hex_coords(rows, True) if num_qubits == 65: - return [ - (0, 0), - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (0, 5), - (0, 6), - (0, 7), - (0, 8), - (0, 9), - (1, 0), - (1, 4), - (1, 8), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (2, 5), - (2, 6), - (2, 7), - (2, 8), - (2, 9), - (2, 10), - (3, 2), - (3, 6), - (3, 10), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - (4, 5), - (4, 6), - (4, 7), - (4, 8), - (4, 9), - (4, 10), - (5, 0), - (5, 4), - (5, 8), - (6, 0), - (6, 1), - (6, 2), - (6, 3), - (6, 4), - (6, 5), - (6, 6), - (6, 7), - (6, 8), - (6, 9), - (6, 10), - (7, 2), - (7, 6), - (7, 10), - (8, 1), - (8, 2), - (8, 3), - (8, 4), - (8, 5), - (8, 6), - (8, 7), - (8, 8), - (8, 9), - (8, 10), + rows = [ + _Row(10, 0, (0, 4, 8)), + r := _Row(11, 0, (2, 6, 10)), + _Row(11, 0, (0, 4, 8)), + r, + _Row(10, 1), ] + return _heavy_hex_coords(rows, True) if num_qubits == 127: - return [ - (0, 0), - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (0, 5), - (0, 6), - (0, 7), - (0, 8), - (0, 9), - (0, 10), - (0, 11), - (0, 12), - (0, 13), - (1, 0), - (1, 4), - (1, 8), - (1, 12), - (2, 0), - (2, 1), - (2, 2), - (2, 3), - (2, 4), - (2, 5), - (2, 6), - (2, 7), - (2, 8), - (2, 9), - (2, 10), - (2, 11), - (2, 12), - (2, 13), - (2, 14), - (3, 2), - (3, 6), - (3, 10), - (3, 14), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - (4, 5), - (4, 6), - (4, 7), - (4, 8), - (4, 9), - (4, 10), - (4, 11), - (4, 12), - (4, 13), - (4, 14), - (5, 0), - (5, 4), - (5, 8), - (5, 12), - (6, 0), - (6, 1), - (6, 2), - (6, 3), - (6, 4), - (6, 5), - (6, 6), - (6, 7), - (6, 8), - (6, 9), - (6, 10), - (6, 11), - (6, 12), - (6, 13), - (6, 14), - (7, 2), - (7, 6), - (7, 10), - (7, 14), - (8, 0), - (8, 1), - (8, 2), - (8, 3), - (8, 4), - (8, 5), - (8, 6), - (8, 7), - (8, 8), - (8, 9), - (8, 10), - (8, 11), - (8, 12), - (8, 13), - (8, 14), - (9, 0), - (9, 4), - (9, 8), - (9, 12), - (10, 0), - (10, 1), - (10, 2), - (10, 3), - (10, 4), - (10, 5), - (10, 6), - (10, 7), - (10, 8), - (10, 9), - (10, 10), - (10, 11), - (10, 12), - (10, 13), - (10, 14), - (11, 2), - (11, 6), - (11, 10), - (11, 14), - (12, 1), - (12, 2), - (12, 3), - (12, 4), - (12, 5), - (12, 6), - (12, 7), - (12, 8), - (12, 9), - (12, 10), - (12, 11), - (12, 12), - (12, 13), - (12, 14), - ] + r1 = _Row(15, 0, (2, 6, 10, 14)) + r2 = _Row(15, 0, (0, 4, 8, 12)) + rows = [_Row(14, 0, (0, 4, 8, 12)), r1, r2, r1, r2, r1, _Row(14, 1)] + return _heavy_hex_coords(rows, True) - return [] + raise ValueError(f"Coordinates for {num_qubits}-qubit CPU are unknown.") diff --git a/test/unit/test_embeddings.py b/test/unit/test_embeddings.py index 514a4ba4e..c6d546665 100644 --- a/test/unit/test_embeddings.py +++ b/test/unit/test_embeddings.py @@ -15,7 +15,7 @@ from qiskit_aer import AerSimulator from qiskit_ibm_runtime.fake_provider.local_service import QiskitRuntimeLocalService -from qiskit_ibm_runtime.utils.embeddings import Embedding +from qiskit_ibm_runtime.utils.embeddings import Embedding, _get_qubits_coordinates from ..ibm_test_case import IBMTestCase @@ -45,14 +45,432 @@ def test_init_error(self): e_vigo = Embedding.from_backend(self.vigo) e_kyiv = Embedding.from_backend(self.kyiv) - with self.assertRaises(ValueError) as e0: + with self.assertRaisesRegex( + ValueError, "Coupling map for backend 'aer_simulator' is unknown." + ): Embedding.from_backend(self.aer) - self.assertEqual(str(e0.exception), "Coupling map for backend 'aer_simulator' is unknown.") - with self.assertRaises(ValueError) as e1: + with self.assertRaisesRegex(ValueError, "Invalid coupling map."): Embedding(e_vigo.coordinates, e_kyiv.coupling_map) - self.assertEqual(str(e1.exception), "Invalid coupling map.") - with self.assertRaises(ValueError) as e2: + with self.assertRaisesRegex(ValueError, "Coordinates for 1-qubit CPU are unknown."): Embedding.from_backend(self.armonk) - self.assertEqual(str(e2.exception), "Coordinates for backend 'fake_armonk' are unknown.") + + +class TestGetCoordinates(IBMTestCase): + """Class for testing the `_get_qubits_coordinates` function.""" + + def test_5(self): + r"""Test for 5-qubit lattices.""" + exp = [(1, 0), (0, 1), (1, 1), (1, 2), (2, 1)] + self.assertListEqual(_get_qubits_coordinates(5), exp) + + def test_7(self): + r"""Test for 7-qubit lattices.""" + exp = [(0, 0), (0, 1), (0, 2), (1, 1), (2, 0), (2, 1), (2, 2)] + self.assertListEqual(_get_qubits_coordinates(7), exp) + + def test_15(self): + r"""Test for 15-qubit lattices.""" + exp = [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 7), + (1, 6), + (1, 5), + (1, 4), + (1, 3), + (1, 2), + (1, 1), + (1, 0), + ] + self.assertListEqual(_get_qubits_coordinates(15), exp) + + def test_16(self): + r"""Test for 16-qubit lattices.""" + exp = [ + (1, 0), + (1, 1), + (2, 1), + (3, 1), + (1, 2), + (3, 2), + (0, 3), + (1, 3), + (3, 3), + (4, 3), + (1, 4), + (3, 4), + (1, 5), + (2, 5), + (3, 5), + (1, 6), + ] + self.assertListEqual(_get_qubits_coordinates(16), exp) + + def test_20(self): + r"""Test for 20-qubit lattices.""" + exp = [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 0), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + ] + self.assertListEqual(_get_qubits_coordinates(20), exp) + + def test_27(self): + r"""Test for 27-qubit lattices.""" + exp = [ + (1, 0), + (1, 1), + (2, 1), + (3, 1), + (1, 2), + (3, 2), + (0, 3), + (1, 3), + (3, 3), + (4, 3), + (1, 4), + (3, 4), + (1, 5), + (2, 5), + (3, 5), + (1, 6), + (3, 6), + (0, 7), + (1, 7), + (3, 7), + (4, 7), + (1, 8), + (3, 8), + (1, 9), + (2, 9), + (3, 9), + (3, 10), + ] + self.assertListEqual(_get_qubits_coordinates(27), exp) + + def test_28(self): + r"""Test for 28-qubit lattices.""" + exp = [ + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 2), + (1, 6), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (2, 8), + (3, 0), + (3, 4), + (3, 8), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (4, 7), + (4, 8), + ] + self.assertListEqual(_get_qubits_coordinates(28), exp) + + def test_53(self): + r"""Test for 53-qubit lattices.""" + exp = [ + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (1, 2), + (1, 6), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (2, 8), + (3, 0), + (3, 4), + (3, 8), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (4, 7), + (4, 8), + (5, 2), + (5, 6), + (6, 0), + (6, 1), + (6, 2), + (6, 3), + (6, 4), + (6, 5), + (6, 6), + (6, 7), + (6, 8), + (7, 0), + (7, 4), + (7, 8), + (8, 0), + (8, 1), + (8, 2), + (8, 3), + (8, 4), + (8, 5), + (8, 6), + (8, 7), + (8, 8), + (9, 2), + (9, 6), + ] + self.assertListEqual(_get_qubits_coordinates(53), exp) + + def test_65(self): + r"""Test for 65-qubit lattices.""" + exp = [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 7), + (0, 8), + (0, 9), + (1, 0), + (1, 4), + (1, 8), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (2, 8), + (2, 9), + (2, 10), + (3, 2), + (3, 6), + (3, 10), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (4, 7), + (4, 8), + (4, 9), + (4, 10), + (5, 0), + (5, 4), + (5, 8), + (6, 0), + (6, 1), + (6, 2), + (6, 3), + (6, 4), + (6, 5), + (6, 6), + (6, 7), + (6, 8), + (6, 9), + (6, 10), + (7, 2), + (7, 6), + (7, 10), + (8, 1), + (8, 2), + (8, 3), + (8, 4), + (8, 5), + (8, 6), + (8, 7), + (8, 8), + (8, 9), + (8, 10), + ] + self.assertListEqual(_get_qubits_coordinates(65), exp) + + def test_127(self): + r"""Test for 127-qubit lattices.""" + exp = [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 7), + (0, 8), + (0, 9), + (0, 10), + (0, 11), + (0, 12), + (0, 13), + (1, 0), + (1, 4), + (1, 8), + (1, 12), + (2, 0), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (2, 8), + (2, 9), + (2, 10), + (2, 11), + (2, 12), + (2, 13), + (2, 14), + (3, 2), + (3, 6), + (3, 10), + (3, 14), + (4, 0), + (4, 1), + (4, 2), + (4, 3), + (4, 4), + (4, 5), + (4, 6), + (4, 7), + (4, 8), + (4, 9), + (4, 10), + (4, 11), + (4, 12), + (4, 13), + (4, 14), + (5, 0), + (5, 4), + (5, 8), + (5, 12), + (6, 0), + (6, 1), + (6, 2), + (6, 3), + (6, 4), + (6, 5), + (6, 6), + (6, 7), + (6, 8), + (6, 9), + (6, 10), + (6, 11), + (6, 12), + (6, 13), + (6, 14), + (7, 2), + (7, 6), + (7, 10), + (7, 14), + (8, 0), + (8, 1), + (8, 2), + (8, 3), + (8, 4), + (8, 5), + (8, 6), + (8, 7), + (8, 8), + (8, 9), + (8, 10), + (8, 11), + (8, 12), + (8, 13), + (8, 14), + (9, 0), + (9, 4), + (9, 8), + (9, 12), + (10, 0), + (10, 1), + (10, 2), + (10, 3), + (10, 4), + (10, 5), + (10, 6), + (10, 7), + (10, 8), + (10, 9), + (10, 10), + (10, 11), + (10, 12), + (10, 13), + (10, 14), + (11, 2), + (11, 6), + (11, 10), + (11, 14), + (12, 1), + (12, 2), + (12, 3), + (12, 4), + (12, 5), + (12, 6), + (12, 7), + (12, 8), + (12, 9), + (12, 10), + (12, 11), + (12, 12), + (12, 13), + (12, 14), + ] + self.assertListEqual(_get_qubits_coordinates(127), exp) + + def test_error(self): + r"""Test that an error is raised when the coordinates are unknown.""" + n = 10**6 # hopefully one day this test will fail + with self.assertRaisesRegex(ValueError, f"Coordinates for {n}-qubit CPU are unknown."): + _get_qubits_coordinates(n) From 2ba278b7f1f19a3813172d51d2b625433e8fafa6 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Wed, 25 Sep 2024 16:35:38 -0400 Subject: [PATCH 2/7] style --- qiskit_ibm_runtime/utils/embeddings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index f5a6196c9..15d29eb32 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -102,7 +102,7 @@ class _Row: down_links: Sequence[int] = () -def _heavy_hex_coords(rows: Sequence[_Row], row_major: bool = True) -> list[tuple[int, int]]: +def _heavy_hex_coords(rows: Sequence[_Row], row_major: bool = True) -> List[Tuple[int, int]]: """Generate heavy hex coordinates for the given rows. Args: From bd273847496b545e2cb7ff0c38920e26c239d8c3 Mon Sep 17 00:00:00 2001 From: Samuele Ferracin Date: Fri, 27 Sep 2024 09:58:28 -0400 Subject: [PATCH 3/7] Update qiskit_ibm_runtime/utils/embeddings.py Co-authored-by: Ian Hincks --- qiskit_ibm_runtime/utils/embeddings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index 15d29eb32..56dc32b71 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -66,8 +66,8 @@ def from_backend(cls, backend: BackendV2) -> "Embedding": coordinates = _get_qubits_coordinates(backend.num_qubits) except ValueError as err: raise ValueError( - f"Failed to fetch coordinates for backend '{backend.name}' with error: {err}" - ) + f"Failed to fetch coordinates for backend '{backend.name}'." + ) from err return cls(coordinates, coupling_map) From 207ac5d16ea27d536a6cd2e8585f2904d2bff63f Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 27 Sep 2024 09:58:42 -0400 Subject: [PATCH 4/7] CR --- qiskit_ibm_runtime/utils/embeddings.py | 84 +++++++++----------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index 15d29eb32..2a28e3694 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -16,7 +16,7 @@ """ from dataclasses import dataclass -from typing import List, Tuple, Union, Sequence +from typing import Iterable, List, Tuple, Union, Sequence from qiskit.providers.backend import BackendV2 from qiskit.transpiler import CouplingMap @@ -86,27 +86,14 @@ def coupling_map(self) -> CouplingMap: return self._coupling_map -@dataclass(frozen=True) -class _Row: - r""" - A class representing a dense row of the standard heavy hex planar embedding. - - Args: - num_qubits: The number of qubits in the dense row. - x_offset: The integer x-offset of the row. - down_links: The positions of dense row, wrt the first qubit, at which to place qubits below. - """ - - num_qubits: int - x_offset: int = 0 - down_links: Sequence[int] = () - - -def _heavy_hex_coords(rows: Sequence[_Row], row_major: bool = True) -> List[Tuple[int, int]]: +def _heavy_hex_coords( + rows: Sequence[Iterable[int]], row_major: bool = True +) -> List[Tuple[int, int]]: """Generate heavy hex coordinates for the given rows. Args: - rows: The dense rows specifications. + rows: A sequence of iterables of integer, where every integer represents the column index + of a qubit in the given row. row_major: Whether qubits should be labelled in row-major order (by ``x`` first and, in case of a tie, by ``y``) or in colum-major order. @@ -117,15 +104,9 @@ def _heavy_hex_coords(rows: Sequence[_Row], row_major: bool = True) -> List[Tupl # Add coordinates in row-major order row_idx = 0 - for row in rows: - if row.num_qubits: - for col_idx in range(row.num_qubits): - coordinates += [(row_idx, col_idx + row.x_offset)] - row_idx += 1 - if row.down_links: - for col_idx in row.down_links: - coordinates += [(row_idx, col_idx + row.x_offset)] - row_idx += 1 + for row_idx, row in enumerate(rows): + for col_idx in row: + coordinates += [(row_idx, col_idx)] # Sort if colum-major order is required if not row_major: @@ -155,7 +136,7 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: return [(1, 0), (0, 1), (1, 1), (1, 2), (2, 1)] if num_qubits == 7: - rows = [_Row(3, 0, (1,)), _Row(3, 0)] + rows = [r := range(3), [1], r] return _heavy_hex_coords(rows) if num_qubits == 15: @@ -178,49 +159,44 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: ] if num_qubits == 16: - rows = [_Row(0, 0, (3,)), _Row(7, 0, (1, 5)), _Row(5, 1, (2,))] + rows = [ + [ + 3, + ], + range(7), + [1, 5], + range(1, 6), + [3], + ] return _heavy_hex_coords(rows, False) if num_qubits == 20: - rows = [row := _Row(5, 0), row, row, row] + rows = [r := range(5), r, r, r] return _heavy_hex_coords(rows) if num_qubits == 27: - rows = [_Row(0, 0, [3, 7]), _Row(10, 0, [1, 5, 9]), _Row(10, 1, [2, 6])] + rows = [[3, 7], range(10), [1, 5, 9], range(1, 11), [3, 7]] return _heavy_hex_coords(rows, False) if num_qubits == 28: - rows = [ - _Row(5, 2, (0, 4)), - _Row(9, 0, (0, 4, 8)), - _Row(9, 0), - ] + rows = [range(2, 7), [2, 6], range(9), [0, 4, 8], range(9)] return _heavy_hex_coords(rows=rows) if num_qubits == 53: - rows = [ - _Row(5, 2, (0, 4)), - r1 := _Row(9, 0, (0, 4, 8)), - r2 := _Row(9, 0, (2, 6)), - r1, - r2, - ] + r1 = [range(9), [0, 4, 8]] + r2 = [range(9), [2, 6]] + rows = [range(2, 7), [2, 6]] + r1 + r2 + r1 + r2 return _heavy_hex_coords(rows, True) if num_qubits == 65: - rows = [ - _Row(10, 0, (0, 4, 8)), - r := _Row(11, 0, (2, 6, 10)), - _Row(11, 0, (0, 4, 8)), - r, - _Row(10, 1), - ] + r = [range(11), [2, 6, 10]] + rows = [range(10), [0, 4, 8]] + r + [range(11), [0, 4, 8]] + r + [range(1, 11)] return _heavy_hex_coords(rows, True) if num_qubits == 127: - r1 = _Row(15, 0, (2, 6, 10, 14)) - r2 = _Row(15, 0, (0, 4, 8, 12)) - rows = [_Row(14, 0, (0, 4, 8, 12)), r1, r2, r1, r2, r1, _Row(14, 1)] + r1 = [range(15), [2, 6, 10, 14]] + r2 = [range(15), [0, 4, 8, 12]] + rows = [range(14), [0, 4, 8, 12]] + r1 + r2 + r1 + r2 + r1 + [range(1, 15)] return _heavy_hex_coords(rows, True) raise ValueError(f"Coordinates for {num_qubits}-qubit CPU are unknown.") From e9b5371765f62a6379113273a7644b2f76bda486 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 27 Sep 2024 09:59:56 -0400 Subject: [PATCH 5/7] CR --- qiskit_ibm_runtime/utils/embeddings.py | 4 +--- test/unit/test_embeddings.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index 4b8542dc7..d0345ea8d 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -65,9 +65,7 @@ def from_backend(cls, backend: BackendV2) -> "Embedding": try: coordinates = _get_qubits_coordinates(backend.num_qubits) except ValueError as err: - raise ValueError( - f"Failed to fetch coordinates for backend '{backend.name}'." - ) from err + raise ValueError(f"Failed to fetch coordinates for backend '{backend.name}'.") from err return cls(coordinates, coupling_map) diff --git a/test/unit/test_embeddings.py b/test/unit/test_embeddings.py index c6d546665..ea8bc8391 100644 --- a/test/unit/test_embeddings.py +++ b/test/unit/test_embeddings.py @@ -53,7 +53,7 @@ def test_init_error(self): with self.assertRaisesRegex(ValueError, "Invalid coupling map."): Embedding(e_vigo.coordinates, e_kyiv.coupling_map) - with self.assertRaisesRegex(ValueError, "Coordinates for 1-qubit CPU are unknown."): + with self.assertRaisesRegex(ValueError, "Failed to fetch coordinates for backend"): Embedding.from_backend(self.armonk) From 478e5a8e55f21db9cdec41d49257a4edb084b4e7 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 27 Sep 2024 10:25:56 -0400 Subject: [PATCH 6/7] lint --- qiskit_ibm_runtime/utils/embeddings.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index d0345ea8d..d13b8cfd2 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -15,7 +15,6 @@ Utility class to represent an embedding of a set of qubits in a two-dimensional plane. """ -from dataclasses import dataclass from typing import Iterable, List, Tuple, Union, Sequence from qiskit.providers.backend import BackendV2 @@ -90,8 +89,8 @@ def _heavy_hex_coords( """Generate heavy hex coordinates for the given rows. Args: - rows: A sequence of iterables of integer, where every integer represents the column index - of a qubit in the given row. + rows: A sequence of rows, sorted from top to bottom. Rows are specified as an iterable of + integers, where every integer represents a column index. row_major: Whether qubits should be labelled in row-major order (by ``x`` first and, in case of a tie, by ``y``) or in colum-major order. From c21ab2d2bfad30b3dcad6d61e9447039c77a39ab Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 27 Sep 2024 10:37:18 -0400 Subject: [PATCH 7/7] mypy --- qiskit_ibm_runtime/utils/embeddings.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/qiskit_ibm_runtime/utils/embeddings.py b/qiskit_ibm_runtime/utils/embeddings.py index d13b8cfd2..929313ddd 100644 --- a/qiskit_ibm_runtime/utils/embeddings.py +++ b/qiskit_ibm_runtime/utils/embeddings.py @@ -133,7 +133,7 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: return [(1, 0), (0, 1), (1, 1), (1, 2), (2, 1)] if num_qubits == 7: - rows = [r := range(3), [1], r] + rows = [range(3), [1], range(3)] return _heavy_hex_coords(rows) if num_qubits == 15: @@ -156,19 +156,11 @@ def _get_qubits_coordinates(num_qubits: int) -> List[Tuple[int, int]]: ] if num_qubits == 16: - rows = [ - [ - 3, - ], - range(7), - [1, 5], - range(1, 6), - [3], - ] + rows = [[3], range(7), [1, 5], range(1, 6), [3]] return _heavy_hex_coords(rows, False) if num_qubits == 20: - rows = [r := range(5), r, r, r] + rows = [range(5)] * 4 return _heavy_hex_coords(rows) if num_qubits == 27: