From 9a5f01b0179342070609a18898bea49550454685 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 13 Sep 2023 12:36:04 +0300 Subject: [PATCH 01/16] adding SabreStartingLayoutUsingVF2 analysis pass --- .../passes/layout/sabre_starting_layout.py | 190 ++++++++++++++++++ test/python/transpiler/test_sabre_layout.py | 32 +++ 2 files changed, 222 insertions(+) create mode 100644 qiskit/transpiler/passes/layout/sabre_starting_layout.py diff --git a/qiskit/transpiler/passes/layout/sabre_starting_layout.py b/qiskit/transpiler/passes/layout/sabre_starting_layout.py new file mode 100644 index 000000000000..9a748167d863 --- /dev/null +++ b/qiskit/transpiler/passes/layout/sabre_starting_layout.py @@ -0,0 +1,190 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Creating Sabre starting layouts.""" + +import itertools + +from qiskit.transpiler import CouplingMap, AnalysisPass +from qiskit.transpiler.passes import VF2Layout +from qiskit._accelerate.error_map import ErrorMap + + +class SabreStartingLayoutUsingVF2(AnalysisPass): + """Choose a starting layout to use for additional Sabre layout trials. + + Property Set Values Written + --------------------------- + + ``sabre_starting_layouts`` (``list[Layout]``) + An optional list of :class:`~.Layout` objects to use for additional Sabre layout trials. + + """ + + def __init__( + self, coupling_map, max_distance=2, error_rate=0.1, max_trials_vf2=100, minimize_edges=True + ): + """SabreStartingLayoutUsingVF2 initializer. + + The pass works by augmenting the coupling map with more and more "extra" edges + until VF2 succeeds to find a perfect graph isomorphism. More precisely, the + augmented coupling map contains edges between nodes that are within a given + distance ``d`` in the original coupling map, and the value of ``d`` is increased + until an isomorphism is found. + + Intuitively, a better layout involves fewer extra edges. The pass also optionally + minimizes the number of extra edges involved in the layout until a local minimum + is found. This involves removing extra edges and running VF2 to see if an + isomorphism still exists. + + Args: + coupling_map (CouplingMap): directed graph representing the original coupling map. + max_distance (int): the maximum distance to consider for augmented coupling maps. + error_rate (float): the error rate to assign to the "extra" edges. A non-zero + error rate prioritizes VF2 to choose original edges over extra edges. + max_trials_vf2 (int): specifies the maximum number of VF2 trials. A larger number + allows VF2 to explore more layouts, eventually choosing the one with the smallest + error rate. + minimize_edges (bool): whether to improve the layout by minimizing the number of + extra edges involved. + + """ + + self.coupling_map = coupling_map + self.max_distance = max_distance + self.error_rate = error_rate + self.max_trials_vf2 = max_trials_vf2 + self.minimize_edges = minimize_edges + super().__init__() + + def run(self, dag): + """Run the SabreStartingLayoutUsingVF2 pass on `dag`. + + The discovered starting layout is written to the property set + value ``sabre_starting_layouts``. + + Args: + dag (DAGCircuit): DAG to create starting layout for. + """ + + starting_layout = None + cur_distance = 1 + while True: + augmented_map, augmented_error_map = self._add_extra_edges(cur_distance) + pass_ = VF2Layout(augmented_map, seed=0, max_trials=self.max_trials_vf2) + pass_.property_set["vf2_avg_error_map"] = augmented_error_map + pass_.run(dag) + + if "layout" in pass_.property_set: + starting_layout = pass_.property_set["layout"] + break + + cur_distance += 1 + if cur_distance > self.max_distance: + break + + if cur_distance > 1 and starting_layout is not None: + # optionally improve starting layout + if self.minimize_edges: + starting_layout = self._minimize_extra_edges(dag, starting_layout) + # write discovered layout into the property set + if "sabre_starting_layout" not in self.property_set: + self.property_set["sabre_starting_layout"] = [starting_layout] + else: + self.property_set["sabre_starting_layout"].append(starting_layout) + + def _add_extra_edges(self, distance): + """Augments the coupling map with extra edges that connect nodes ``distance`` + apart in the original graph. The extra edges are assigned errors allowing VF2 + to prioritize real edges over extra edges. + """ + nq = len(self.coupling_map.graph.node_indices()) + augmented_coupling_map = CouplingMap() + augmented_error_map = ErrorMap(nq) + + for node in self.coupling_map.graph.node_indices(): + augmented_coupling_map.add_physical_qubit(node) + + for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): + d = self.coupling_map.distance(x, y) + if 0 < d <= distance: + error = 0 if d == 1 else self.error_rate + augmented_coupling_map.add_edge(x, y) + augmented_error_map.add_error((x, y), error) + augmented_coupling_map.add_edge(y, x) + augmented_error_map.add_error((y, x), error) + return augmented_coupling_map, augmented_error_map + + def _get_extra_edges_used(self, dag, layout): + """Returns the list of extra edges involved in the layout.""" + extra_edges_used = [] + virtual_bits = layout.get_virtual_bits() + for node in dag.op_nodes(): + if len(node.qargs) == 2: + p0 = virtual_bits[node.qargs[0]] + p1 = virtual_bits[node.qargs[1]] + if self.coupling_map.distance(p0, p1) > 1: + extra_edge = (p0, p1) if p0 < p1 else (p1, p0) + extra_edges_used.append(extra_edge) + return extra_edges_used + + def _find_layout(self, dag, edges): + """Checks if there is a layout for a given set of edges.""" + cm = CouplingMap(edges) + pass_ = VF2Layout(cm, seed=0, max_trials=1) + pass_.run(dag) + return pass_.property_set.get("layout", None) + + def _minimize_extra_edges(self, dag, starting_layout): + """Minimizes the set of extra edges involved in the layout. This iteratively + removes extra edges from the coupling map and uses VF2 to check if a layout + still exists. This is reasonably efficiently as it only looks for a local + minimum. + """ + # compute the set of edges in the original coupling map + real_edges = [] + for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): + d = self.coupling_map.distance(x, y) + if d == 1: + real_edges.append((x, y)) + + best_layout = starting_layout + + # keeps the set of "necessary" extra edges: without a necessary edge + # a layout no longer exists + extra_edges_necessary = [] + + extra_edges = self._get_extra_edges_used(dag, starting_layout) + extra_edges_unprocessed_set = set(extra_edges) + + while extra_edges_unprocessed_set: + # choose some unprocessed edge + edge_chosen = next(iter(extra_edges_unprocessed_set)) + extra_edges_unprocessed_set.remove(edge_chosen) + + # check if a layout still exists without this edge + layout = self._find_layout( + dag, real_edges + extra_edges_necessary + list(extra_edges_unprocessed_set) + ) + + if layout is None: + # this edge is necessary + extra_edges_necessary.append(edge_chosen) + + else: + # this edge is not necessary, furthermore we can trim the set of edges to examine based + # in the edges involved in the layout. + extra_edges = self._get_extra_edges_used(dag, layout) + extra_edges_unprocessed_set = set(extra_edges) + best_layout = layout + + return best_layout diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 38c17b442964..847d772b7673 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -14,7 +14,10 @@ import unittest +import math + from qiskit import QuantumRegister, QuantumCircuit +from qiskit.circuit.library import EfficientSU2 from qiskit.transpiler import CouplingMap, AnalysisPass, PassManager from qiskit.transpiler.passes import SabreLayout, DenseLayout from qiskit.transpiler.exceptions import TranspilerError @@ -24,6 +27,7 @@ from qiskit.providers.fake_provider import FakeAlmaden, FakeAlmadenV2 from qiskit.providers.fake_provider import FakeKolkata from qiskit.providers.fake_provider import FakeMontreal +from qiskit.transpiler.passes.layout.sabre_starting_layout import SabreStartingLayoutUsingVF2 class TestSabreLayout(QiskitTestCase): @@ -389,5 +393,33 @@ def test_with_partial_layout(self): self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8]) +class TestSabreStartingLayoutUsingVF2(QiskitTestCase): + """Tests the SabreLayout pass with starting layout created by SabreStartingLayoutUsingVF2.""" + + def setUp(self): + super().setUp() + circuit = EfficientSU2(16, entanglement="circular", reps=6, flatten=True) + circuit.assign_parameters([math.pi / 2] * len(circuit.parameters), inplace=True) + circuit.measure_all() + self.circuit = circuit + self.coupling_map = CouplingMap.from_heavy_hex(7) + + def test_starting_layout(self): + """Test that a starting layout is created and looks as expected.""" + pm = PassManager( + [ + SabreStartingLayoutUsingVF2(coupling_map=self.coupling_map), + SabreLayout(self.coupling_map, seed=123456, swap_trials=1, layout_trials=1), + ] + ) + pm.run(self.circuit) + layout = pm.property_set["layout"] + print([layout[q] for q in self.circuit.qubits]) + self.assertEqual( + [layout[q] for q in self.circuit.qubits], + [54, 87, 17, 88, 93, 62, 99, 31, 100, 32, 63, 25, 24, 94, 59, 81], + ) + + if __name__ == "__main__": unittest.main() From 625d857100c9b685066db3253520032e8beb2fbd Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 13 Sep 2023 13:15:34 +0300 Subject: [PATCH 02/16] fixing imports --- qiskit/transpiler/passes/__init__.py | 2 ++ qiskit/transpiler/passes/layout/__init__.py | 1 + qiskit/transpiler/passes/layout/sabre_starting_layout.py | 8 ++++---- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index ebf095b54edc..3f9f48130a6d 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -34,6 +34,7 @@ Layout2qDistance EnlargeWithAncilla FullAncillaAllocation + SabreStartingLayoutUsingVF2 Routing ======= @@ -191,6 +192,7 @@ from .layout import Layout2qDistance from .layout import EnlargeWithAncilla from .layout import FullAncillaAllocation +from .layout import SabreStartingLayoutUsingVF2 # routing from .routing import BasicSwap diff --git a/qiskit/transpiler/passes/layout/__init__.py b/qiskit/transpiler/passes/layout/__init__.py index 736ea3add2f4..560c7836c189 100644 --- a/qiskit/transpiler/passes/layout/__init__.py +++ b/qiskit/transpiler/passes/layout/__init__.py @@ -24,3 +24,4 @@ from .layout_2q_distance import Layout2qDistance from .enlarge_with_ancilla import EnlargeWithAncilla from .full_ancilla_allocation import FullAncillaAllocation +from .sabre_starting_layout import SabreStartingLayoutUsingVF2 diff --git a/qiskit/transpiler/passes/layout/sabre_starting_layout.py b/qiskit/transpiler/passes/layout/sabre_starting_layout.py index 9a748167d863..f79b8e0c2434 100644 --- a/qiskit/transpiler/passes/layout/sabre_starting_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_starting_layout.py @@ -15,7 +15,7 @@ import itertools from qiskit.transpiler import CouplingMap, AnalysisPass -from qiskit.transpiler.passes import VF2Layout +from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout from qiskit._accelerate.error_map import ErrorMap @@ -97,10 +97,10 @@ def run(self, dag): if self.minimize_edges: starting_layout = self._minimize_extra_edges(dag, starting_layout) # write discovered layout into the property set - if "sabre_starting_layout" not in self.property_set: - self.property_set["sabre_starting_layout"] = [starting_layout] + if "sabre_starting_layouts" not in self.property_set: + self.property_set["sabre_starting_layouts"] = [starting_layout] else: - self.property_set["sabre_starting_layout"].append(starting_layout) + self.property_set["sabre_starting_layouts"].append(starting_layout) def _add_extra_edges(self, distance): """Augments the coupling map with extra edges that connect nodes ``distance`` From c24439398699e8458fb139dd9ddf84de8ab0ec86 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 13 Sep 2023 13:46:39 +0300 Subject: [PATCH 03/16] bug fix --- qiskit/transpiler/passes/layout/sabre_starting_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/layout/sabre_starting_layout.py b/qiskit/transpiler/passes/layout/sabre_starting_layout.py index f79b8e0c2434..5840e7b8ffdb 100644 --- a/qiskit/transpiler/passes/layout/sabre_starting_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_starting_layout.py @@ -184,7 +184,7 @@ def _minimize_extra_edges(self, dag, starting_layout): # this edge is not necessary, furthermore we can trim the set of edges to examine based # in the edges involved in the layout. extra_edges = self._get_extra_edges_used(dag, layout) - extra_edges_unprocessed_set = set(extra_edges) + extra_edges_unprocessed_set = set(extra_edges).difference(set(extra_edges_necessary)) best_layout = layout return best_layout From be118a657587c7f21303e738b5a9cba7251967a3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 13 Sep 2023 14:03:24 +0300 Subject: [PATCH 04/16] fixing test after changing some of the options --- test/python/transpiler/test_sabre_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 847d772b7673..ed6a7154b09d 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -417,7 +417,7 @@ def test_starting_layout(self): print([layout[q] for q in self.circuit.qubits]) self.assertEqual( [layout[q] for q in self.circuit.qubits], - [54, 87, 17, 88, 93, 62, 99, 31, 100, 32, 63, 25, 24, 94, 59, 81], + [30, 98, 104, 36, 103, 35, 65, 28, 61, 91, 22, 92, 23, 93, 62, 99], ) From 6e9e1132699f81a212b1f73130fe52c43619c4e9 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 15 Sep 2023 08:47:50 +0300 Subject: [PATCH 05/16] renaming --- qiskit/transpiler/passes/__init__.py | 4 ++-- qiskit/transpiler/passes/layout/__init__.py | 2 +- .../{sabre_starting_layout.py => sabre_pre_layout.py} | 10 ++++++---- test/python/transpiler/test_sabre_layout.py | 8 ++++---- 4 files changed, 13 insertions(+), 11 deletions(-) rename qiskit/transpiler/passes/layout/{sabre_starting_layout.py => sabre_pre_layout.py} (97%) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 3f9f48130a6d..01fc9ad757e5 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -34,7 +34,7 @@ Layout2qDistance EnlargeWithAncilla FullAncillaAllocation - SabreStartingLayoutUsingVF2 + SabrePreLayout Routing ======= @@ -192,7 +192,7 @@ from .layout import Layout2qDistance from .layout import EnlargeWithAncilla from .layout import FullAncillaAllocation -from .layout import SabreStartingLayoutUsingVF2 +from .layout import SabrePreLayout # routing from .routing import BasicSwap diff --git a/qiskit/transpiler/passes/layout/__init__.py b/qiskit/transpiler/passes/layout/__init__.py index 560c7836c189..9b0fdc079d2c 100644 --- a/qiskit/transpiler/passes/layout/__init__.py +++ b/qiskit/transpiler/passes/layout/__init__.py @@ -24,4 +24,4 @@ from .layout_2q_distance import Layout2qDistance from .enlarge_with_ancilla import EnlargeWithAncilla from .full_ancilla_allocation import FullAncillaAllocation -from .sabre_starting_layout import SabreStartingLayoutUsingVF2 +from .sabre_pre_layout import SabrePreLayout diff --git a/qiskit/transpiler/passes/layout/sabre_starting_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py similarity index 97% rename from qiskit/transpiler/passes/layout/sabre_starting_layout.py rename to qiskit/transpiler/passes/layout/sabre_pre_layout.py index 5840e7b8ffdb..c3969aff0d0c 100644 --- a/qiskit/transpiler/passes/layout/sabre_starting_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -19,7 +19,7 @@ from qiskit._accelerate.error_map import ErrorMap -class SabreStartingLayoutUsingVF2(AnalysisPass): +class SabrePreLayout(AnalysisPass): """Choose a starting layout to use for additional Sabre layout trials. Property Set Values Written @@ -33,7 +33,7 @@ class SabreStartingLayoutUsingVF2(AnalysisPass): def __init__( self, coupling_map, max_distance=2, error_rate=0.1, max_trials_vf2=100, minimize_edges=True ): - """SabreStartingLayoutUsingVF2 initializer. + """SabrePreLayout initializer. The pass works by augmenting the coupling map with more and more "extra" edges until VF2 succeeds to find a perfect graph isomorphism. More precisely, the @@ -67,7 +67,7 @@ def __init__( super().__init__() def run(self, dag): - """Run the SabreStartingLayoutUsingVF2 pass on `dag`. + """Run the SabrePreLayout pass on `dag`. The discovered starting layout is written to the property set value ``sabre_starting_layouts``. @@ -184,7 +184,9 @@ def _minimize_extra_edges(self, dag, starting_layout): # this edge is not necessary, furthermore we can trim the set of edges to examine based # in the edges involved in the layout. extra_edges = self._get_extra_edges_used(dag, layout) - extra_edges_unprocessed_set = set(extra_edges).difference(set(extra_edges_necessary)) + extra_edges_unprocessed_set = set(extra_edges).difference( + set(extra_edges_necessary) + ) best_layout = layout return best_layout diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index ed6a7154b09d..42ecd4e360c9 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -27,7 +27,7 @@ from qiskit.providers.fake_provider import FakeAlmaden, FakeAlmadenV2 from qiskit.providers.fake_provider import FakeKolkata from qiskit.providers.fake_provider import FakeMontreal -from qiskit.transpiler.passes.layout.sabre_starting_layout import SabreStartingLayoutUsingVF2 +from qiskit.transpiler.passes.layout.sabre_pre_layout import SabrePreLayout class TestSabreLayout(QiskitTestCase): @@ -393,8 +393,8 @@ def test_with_partial_layout(self): self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8]) -class TestSabreStartingLayoutUsingVF2(QiskitTestCase): - """Tests the SabreLayout pass with starting layout created by SabreStartingLayoutUsingVF2.""" +class TestSabrePreLayout(QiskitTestCase): + """Tests the SabreLayout pass with starting layout created by SabrePreLayout.""" def setUp(self): super().setUp() @@ -408,7 +408,7 @@ def test_starting_layout(self): """Test that a starting layout is created and looks as expected.""" pm = PassManager( [ - SabreStartingLayoutUsingVF2(coupling_map=self.coupling_map), + SabrePreLayout(coupling_map=self.coupling_map), SabreLayout(self.coupling_map, seed=123456, swap_trials=1, layout_trials=1), ] ) From 7df9fafbd566888a195db7fbebd2fbc222726819 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 15 Sep 2023 10:03:43 +0300 Subject: [PATCH 06/16] adding target; more renaming; tests --- .../passes/layout/sabre_pre_layout.py | 33 ++++++-- .../transpiler/test_sabre_pre_layout.py | 84 +++++++++++++++++++ 2 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 test/python/transpiler/test_sabre_pre_layout.py diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index c3969aff0d0c..48573875e11f 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -14,7 +14,7 @@ import itertools -from qiskit.transpiler import CouplingMap, AnalysisPass +from qiskit.transpiler import CouplingMap, AnalysisPass, TranspilerError from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout from qiskit._accelerate.error_map import ErrorMap @@ -31,7 +31,13 @@ class SabrePreLayout(AnalysisPass): """ def __init__( - self, coupling_map, max_distance=2, error_rate=0.1, max_trials_vf2=100, minimize_edges=True + self, + target=None, + coupling_map=None, + max_distance=2, + error_rate=0.1, + max_trials_vf2=100, + improve_layout=True, ): """SabrePreLayout initializer. @@ -47,6 +53,8 @@ def __init__( isomorphism still exists. Args: + target (Target): A target representing the backend device. If specified, it will + supersede a set value for ``coupling_map``. coupling_map (CouplingMap): directed graph representing the original coupling map. max_distance (int): the maximum distance to consider for augmented coupling maps. error_rate (float): the error rate to assign to the "extra" edges. A non-zero @@ -54,16 +62,24 @@ def __init__( max_trials_vf2 (int): specifies the maximum number of VF2 trials. A larger number allows VF2 to explore more layouts, eventually choosing the one with the smallest error rate. - minimize_edges (bool): whether to improve the layout by minimizing the number of - extra edges involved. + improve_layout (bool): whether to improve the layout by minimizing the number of + extra edges involved. This might be time-consuming as this requires additional + VF2 calls. + Raises: + TranspilerError: At runtime, if neither ``coupling_map`` or ``target`` are provided. """ + self.target = target self.coupling_map = coupling_map self.max_distance = max_distance self.error_rate = error_rate self.max_trials_vf2 = max_trials_vf2 - self.minimize_edges = minimize_edges + self.improve_layout = improve_layout + + if self.target is not None: + self.coupling_map = self.target.build_coupling_map() + super().__init__() def run(self, dag): @@ -76,6 +92,11 @@ def run(self, dag): dag (DAGCircuit): DAG to create starting layout for. """ + if self.coupling_map is None: + raise TranspilerError( + "SabrePreLayout requires either target or coupling_map to be provided." + ) + starting_layout = None cur_distance = 1 while True: @@ -94,7 +115,7 @@ def run(self, dag): if cur_distance > 1 and starting_layout is not None: # optionally improve starting layout - if self.minimize_edges: + if self.improve_layout: starting_layout = self._minimize_extra_edges(dag, starting_layout) # write discovered layout into the property set if "sabre_starting_layouts" not in self.property_set: diff --git a/test/python/transpiler/test_sabre_pre_layout.py b/test/python/transpiler/test_sabre_pre_layout.py new file mode 100644 index 000000000000..b93636ed3e42 --- /dev/null +++ b/test/python/transpiler/test_sabre_pre_layout.py @@ -0,0 +1,84 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the SabrePreLayout pass""" + +from qiskit.circuit import QuantumCircuit +from qiskit.transpiler import TranspilerError, CouplingMap, PassManager +from qiskit.transpiler.passes.layout.sabre_pre_layout import SabrePreLayout +from qiskit.converters import circuit_to_dag +from qiskit.test import QiskitTestCase + + +class TestSabrePreLayout(QiskitTestCase): + """Tests the SabrePreLayout pass.""" + + def test_no_constraints(self): + """Test we raise at runtime if no target or coupling graph are provided.""" + qc = QuantumCircuit(2) + empty_pass = SabrePreLayout() + with self.assertRaises(TranspilerError): + empty_pass.run(circuit_to_dag(qc)) + + def test_starting_layout_created(self): + """Test the case that no perfect layout exists and SabrePreLayout can find a + starting layout.""" + qc = QuantumCircuit(4) + qc.cx(0, 1) + qc.cx(1, 2) + qc.cx(2, 3) + qc.cx(3, 0) + coupling_map = CouplingMap.from_ring(5) + pm = PassManager([SabrePreLayout(coupling_map=coupling_map)]) + pm.run(qc) + + # SabrePreLayout should discover a single layout. + self.assertIn("sabre_starting_layouts", pm.property_set) + layouts = pm.property_set["sabre_starting_layouts"] + self.assertEqual(len(layouts), 1) + layout = layouts[0] + self.assertEqual([layout[q] for q in qc.qubits], [2, 1, 0, 4]) + + def test_perfect_layout_exists(self): + """Test the case that a perfect layout exists.""" + qc = QuantumCircuit(4) + qc.cx(0, 1) + qc.cx(1, 2) + qc.cx(2, 3) + qc.cx(3, 0) + coupling_map = CouplingMap.from_ring(4) + pm = PassManager([SabrePreLayout(coupling_map=coupling_map)]) + pm.run(qc) + + # SabrePreLayout should not create starting layouts when a perfect layout exists. + self.assertNotIn("sabre_starting_layouts", pm.property_set) + + def test_max_distance(self): + """Test the ``max_distance`` option to SabrePreLayout.""" + qc = QuantumCircuit(6) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.cx(0, 4) + qc.cx(0, 5) + coupling_map = CouplingMap.from_ring(6) + + # It is not possible to map a star-graph with 5 leaves into a ring with 6 nodes, + # so that all nodes are distance-2 apart. + pm = PassManager([SabrePreLayout(coupling_map=coupling_map, max_distance=2)]) + pm.run(qc) + self.assertNotIn("sabre_starting_layouts", pm.property_set) + + # But possible with distance-3. + pm = PassManager([SabrePreLayout(coupling_map=coupling_map, max_distance=3)]) + pm.run(qc) + self.assertIn("sabre_starting_layouts", pm.property_set) From 3982e0332e4af225e42791a05d41d45fb3429bbc Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 15 Sep 2023 10:35:03 +0300 Subject: [PATCH 07/16] release notes --- ...abre-starting-layout-7e151b7abb8a6c13.yaml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 releasenotes/notes/add-sabre-starting-layout-7e151b7abb8a6c13.yaml diff --git a/releasenotes/notes/add-sabre-starting-layout-7e151b7abb8a6c13.yaml b/releasenotes/notes/add-sabre-starting-layout-7e151b7abb8a6c13.yaml new file mode 100644 index 000000000000..98c2467bb844 --- /dev/null +++ b/releasenotes/notes/add-sabre-starting-layout-7e151b7abb8a6c13.yaml @@ -0,0 +1,37 @@ +--- +features: + - | + Added a new analysis :class:`.SabrePreLayout` pass that creates a starting + layout for :class:`.SabreLayout`, writing the layout into the property set + value ``sabre_starting_layouts``. + + The pass works by augmenting the coupling map with more and more "extra" edges + until :class:`.VF2Layout` succeeds to find a perfect graph isomorphism. + More precisely, the augmented coupling map contains edges between nodes that are + within a given distance ``d`` in the original coupling map, and the value of ``d`` + is increased until an isomorphism is found. The pass also optionally minimizes + the number of extra edges involved in the layout until a local minimum is found. + This involves removing extra edges and calling :class:`.VF2Layout` to check if + an isomorphism still exists. + + Here is an example of calling the :class:`.SabrePreLayout` before :class:`.SabreLayout`:: + + import math + from qiskit.transpiler import CouplingMap, PassManager + from qiskit.circuit.library import EfficientSU2 + from qiskit.transpiler.passes import SabrePreLayout, SabreLayout + + qc = EfficientSU2(16, entanglement='circular', reps=6, flatten=True) + qc.assign_parameters([math.pi / 2] * len(qc.parameters), inplace=True) + qc.measure_all() + + coupling_map = CouplingMap.from_heavy_hex(7) + + pm = PassManager( + [ + SabrePreLayout(coupling_map=coupling_map), + SabreLayout(coupling_map), + ] + ) + + pm.run(qc) From f006dcd67978b7d0e1719428ec90aaf54053015f Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 20 Sep 2023 17:14:09 +0300 Subject: [PATCH 08/16] applying suggestions from code review --- .../passes/layout/sabre_pre_layout.py | 30 +++++++++++-------- .../transpiler/test_sabre_pre_layout.py | 14 +++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index 48573875e11f..cb5c55a56543 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -37,6 +37,7 @@ def __init__( max_distance=2, error_rate=0.1, max_trials_vf2=100, + call_limit_vf2=None, improve_layout=True, ): """SabrePreLayout initializer. @@ -62,6 +63,7 @@ def __init__( max_trials_vf2 (int): specifies the maximum number of VF2 trials. A larger number allows VF2 to explore more layouts, eventually choosing the one with the smallest error rate. + call_limit_vf2 (int): limits each call to VF2 by bounding the number of VF2 state visits. improve_layout (bool): whether to improve the layout by minimizing the number of extra edges involved. This might be time-consuming as this requires additional VF2 calls. @@ -75,6 +77,7 @@ def __init__( self.max_distance = max_distance self.error_rate = error_rate self.max_trials_vf2 = max_trials_vf2 + self.call_limit_vf2 = call_limit_vf2 self.improve_layout = improve_layout if self.target is not None: @@ -99,9 +102,14 @@ def run(self, dag): starting_layout = None cur_distance = 1 - while True: + while cur_distance <= self.max_distance: augmented_map, augmented_error_map = self._add_extra_edges(cur_distance) - pass_ = VF2Layout(augmented_map, seed=0, max_trials=self.max_trials_vf2) + pass_ = VF2Layout( + augmented_map, + seed=0, + max_trials=self.max_trials_vf2, + call_limit=self.call_limit_vf2, + ) pass_.property_set["vf2_avg_error_map"] = augmented_error_map pass_.run(dag) @@ -110,8 +118,6 @@ def run(self, dag): break cur_distance += 1 - if cur_distance > self.max_distance: - break if cur_distance > 1 and starting_layout is not None: # optionally improve starting layout @@ -130,19 +136,17 @@ def _add_extra_edges(self, distance): """ nq = len(self.coupling_map.graph.node_indices()) augmented_coupling_map = CouplingMap() + augmented_coupling_map.graph = self.coupling_map.graph.copy() augmented_error_map = ErrorMap(nq) - for node in self.coupling_map.graph.node_indices(): - augmented_coupling_map.add_physical_qubit(node) - for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): d = self.coupling_map.distance(x, y) - if 0 < d <= distance: - error = 0 if d == 1 else self.error_rate + if 1 < d <= distance: augmented_coupling_map.add_edge(x, y) - augmented_error_map.add_error((x, y), error) + augmented_error_map.add_error((x, y), self.error_rate) augmented_coupling_map.add_edge(y, x) - augmented_error_map.add_error((y, x), error) + augmented_error_map.add_error((y, x), self.error_rate) + return augmented_coupling_map, augmented_error_map def _get_extra_edges_used(self, dag, layout): @@ -161,7 +165,7 @@ def _get_extra_edges_used(self, dag, layout): def _find_layout(self, dag, edges): """Checks if there is a layout for a given set of edges.""" cm = CouplingMap(edges) - pass_ = VF2Layout(cm, seed=0, max_trials=1) + pass_ = VF2Layout(cm, seed=0, max_trials=1, call_limit=self.call_limit_vf2) pass_.run(dag) return pass_.property_set.get("layout", None) @@ -198,7 +202,7 @@ def _minimize_extra_edges(self, dag, starting_layout): ) if layout is None: - # this edge is necessary + # without this edge the layout either does not exist or is too hard to find extra_edges_necessary.append(edge_chosen) else: diff --git a/test/python/transpiler/test_sabre_pre_layout.py b/test/python/transpiler/test_sabre_pre_layout.py index b93636ed3e42..1e548cf7f940 100644 --- a/test/python/transpiler/test_sabre_pre_layout.py +++ b/test/python/transpiler/test_sabre_pre_layout.py @@ -82,3 +82,17 @@ def test_max_distance(self): pm = PassManager([SabrePreLayout(coupling_map=coupling_map, max_distance=3)]) pm.run(qc) self.assertIn("sabre_starting_layouts", pm.property_set) + + def test_call_limit_vf2(self): + """Test the ``call_limit_vf2`` option to SabrePreLayout.""" + qc = QuantumCircuit(4) + qc.cx(0, 1) + qc.cx(1, 2) + qc.cx(2, 3) + qc.cx(3, 0) + coupling_map = CouplingMap.from_ring(5) + pm = PassManager( + [SabrePreLayout(coupling_map=coupling_map, call_limit_vf2=1, max_distance=3)] + ) + pm.run(qc) + self.assertNotIn("sabre_starting_layouts", pm.property_set) From e02f8590252f5ba11825c8eacf50e2e55dce0f17 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 20 Sep 2023 18:22:22 +0300 Subject: [PATCH 09/16] removing debug print --- test/python/transpiler/test_sabre_layout.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 42ecd4e360c9..63fe287c4a17 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -414,7 +414,6 @@ def test_starting_layout(self): ) pm.run(self.circuit) layout = pm.property_set["layout"] - print([layout[q] for q in self.circuit.qubits]) self.assertEqual( [layout[q] for q in self.circuit.qubits], [30, 98, 104, 36, 103, 35, 65, 28, 61, 91, 22, 92, 23, 93, 62, 99], From 64e89d1ca75baa9781b0f4c18714254e9cd558fa Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Fri, 29 Sep 2023 11:32:50 +0300 Subject: [PATCH 10/16] Update qiskit/transpiler/passes/layout/sabre_pre_layout.py I didn't know this existed :) Co-authored-by: Matthew Treinish --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index cb5c55a56543..eb9a406017f3 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -153,8 +153,7 @@ def _get_extra_edges_used(self, dag, layout): """Returns the list of extra edges involved in the layout.""" extra_edges_used = [] virtual_bits = layout.get_virtual_bits() - for node in dag.op_nodes(): - if len(node.qargs) == 2: + for node in dag.two_qubit_ops() p0 = virtual_bits[node.qargs[0]] p1 = virtual_bits[node.qargs[1]] if self.coupling_map.distance(p0, p1) > 1: From c91fac125af2377c5f5f7d2cf966dbb155dd2155 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 29 Sep 2023 11:41:50 +0300 Subject: [PATCH 11/16] adding missing : --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index eb9a406017f3..021f8be7e5b7 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -153,12 +153,12 @@ def _get_extra_edges_used(self, dag, layout): """Returns the list of extra edges involved in the layout.""" extra_edges_used = [] virtual_bits = layout.get_virtual_bits() - for node in dag.two_qubit_ops() - p0 = virtual_bits[node.qargs[0]] - p1 = virtual_bits[node.qargs[1]] - if self.coupling_map.distance(p0, p1) > 1: - extra_edge = (p0, p1) if p0 < p1 else (p1, p0) - extra_edges_used.append(extra_edge) + for node in dag.two_qubit_ops(): + p0 = virtual_bits[node.qargs[0]] + p1 = virtual_bits[node.qargs[1]] + if self.coupling_map.distance(p0, p1) > 1: + extra_edge = (p0, p1) if p0 < p1 else (p1, p0) + extra_edges_used.append(extra_edge) return extra_edges_used def _find_layout(self, dag, edges): From 7235ef7b6b5c65db86ce4a51e891adcb042ccea5 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 29 Sep 2023 11:48:14 +0300 Subject: [PATCH 12/16] collecting edges into a set --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index 021f8be7e5b7..4656a76c0d1c 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -150,15 +150,15 @@ def _add_extra_edges(self, distance): return augmented_coupling_map, augmented_error_map def _get_extra_edges_used(self, dag, layout): - """Returns the list of extra edges involved in the layout.""" - extra_edges_used = [] + """Returns the set of extra edges involved in the layout.""" + extra_edges_used = set() virtual_bits = layout.get_virtual_bits() for node in dag.two_qubit_ops(): p0 = virtual_bits[node.qargs[0]] p1 = virtual_bits[node.qargs[1]] if self.coupling_map.distance(p0, p1) > 1: extra_edge = (p0, p1) if p0 < p1 else (p1, p0) - extra_edges_used.append(extra_edge) + extra_edges_used.add(extra_edge) return extra_edges_used def _find_layout(self, dag, edges): @@ -187,8 +187,7 @@ def _minimize_extra_edges(self, dag, starting_layout): # a layout no longer exists extra_edges_necessary = [] - extra_edges = self._get_extra_edges_used(dag, starting_layout) - extra_edges_unprocessed_set = set(extra_edges) + extra_edges_unprocessed_set = self._get_extra_edges_used(dag, starting_layout) while extra_edges_unprocessed_set: # choose some unprocessed edge @@ -207,8 +206,7 @@ def _minimize_extra_edges(self, dag, starting_layout): else: # this edge is not necessary, furthermore we can trim the set of edges to examine based # in the edges involved in the layout. - extra_edges = self._get_extra_edges_used(dag, layout) - extra_edges_unprocessed_set = set(extra_edges).difference( + extra_edges_unprocessed_set = self._get_extra_edges_used(dag, layout).difference( set(extra_edges_necessary) ) best_layout = layout From 668e2bd3b269aa0e8dacb09edc6dc9d4418255ce Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 12 Oct 2023 12:21:32 +0300 Subject: [PATCH 13/16] adjusting error_rate with respect to distance --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index 4656a76c0d1c..a44ad9818356 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -142,10 +142,11 @@ def _add_extra_edges(self, distance): for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): d = self.coupling_map.distance(x, y) if 1 < d <= distance: + error_rate = 1 - ((1 - self.error_rate) ** d) augmented_coupling_map.add_edge(x, y) - augmented_error_map.add_error((x, y), self.error_rate) + augmented_error_map.add_error((x, y), error_rate) augmented_coupling_map.add_edge(y, x) - augmented_error_map.add_error((y, x), self.error_rate) + augmented_error_map.add_error((y, x), error_rate) return augmented_coupling_map, augmented_error_map From 96b1056c9b080d1f27d92ec892b60b035d2da7c6 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 12 Oct 2023 14:00:00 +0300 Subject: [PATCH 14/16] letting coupling_map be either coupling map or target, for consistency with other passes --- .../transpiler/passes/layout/sabre_pre_layout.py | 16 +++++++++------- test/python/transpiler/test_sabre_pre_layout.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index a44ad9818356..2596a133ee3c 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -14,7 +14,7 @@ import itertools -from qiskit.transpiler import CouplingMap, AnalysisPass, TranspilerError +from qiskit.transpiler import CouplingMap, Target, AnalysisPass, TranspilerError from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout from qiskit._accelerate.error_map import ErrorMap @@ -32,8 +32,7 @@ class SabrePreLayout(AnalysisPass): def __init__( self, - target=None, - coupling_map=None, + coupling_map, max_distance=2, error_rate=0.1, max_trials_vf2=100, @@ -56,7 +55,8 @@ def __init__( Args: target (Target): A target representing the backend device. If specified, it will supersede a set value for ``coupling_map``. - coupling_map (CouplingMap): directed graph representing the original coupling map. + coupling_map (Union[CouplingMap, Target]): directed graph representing the + original coupling map. max_distance (int): the maximum distance to consider for augmented coupling maps. error_rate (float): the error rate to assign to the "extra" edges. A non-zero error rate prioritizes VF2 to choose original edges over extra edges. @@ -72,16 +72,18 @@ def __init__( TranspilerError: At runtime, if neither ``coupling_map`` or ``target`` are provided. """ - self.target = target - self.coupling_map = coupling_map self.max_distance = max_distance self.error_rate = error_rate self.max_trials_vf2 = max_trials_vf2 self.call_limit_vf2 = call_limit_vf2 self.improve_layout = improve_layout - if self.target is not None: + if isinstance(coupling_map, Target): + self.target = coupling_map self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map super().__init__() diff --git a/test/python/transpiler/test_sabre_pre_layout.py b/test/python/transpiler/test_sabre_pre_layout.py index 1e548cf7f940..4041cb50b654 100644 --- a/test/python/transpiler/test_sabre_pre_layout.py +++ b/test/python/transpiler/test_sabre_pre_layout.py @@ -25,7 +25,7 @@ class TestSabrePreLayout(QiskitTestCase): def test_no_constraints(self): """Test we raise at runtime if no target or coupling graph are provided.""" qc = QuantumCircuit(2) - empty_pass = SabrePreLayout() + empty_pass = SabrePreLayout(coupling_map=None) with self.assertRaises(TranspilerError): empty_pass.run(circuit_to_dag(qc)) From 5e6d3a5d1257ad00dc76257f6aae3cf9d4714da5 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 16 Oct 2023 13:28:13 +0300 Subject: [PATCH 15/16] apply suggestions from code review --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 8 ++++---- test/python/transpiler/test_sabre_layout.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index 2596a133ee3c..7d666ee3edf9 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -53,10 +53,9 @@ def __init__( isomorphism still exists. Args: - target (Target): A target representing the backend device. If specified, it will - supersede a set value for ``coupling_map``. coupling_map (Union[CouplingMap, Target]): directed graph representing the - original coupling map. + original coupling map or a target modelling the backend (including its + connectivity). max_distance (int): the maximum distance to consider for augmented coupling maps. error_rate (float): the error rate to assign to the "extra" edges. A non-zero error rate prioritizes VF2 to choose original edges over extra edges. @@ -99,7 +98,8 @@ def run(self, dag): if self.coupling_map is None: raise TranspilerError( - "SabrePreLayout requires either target or coupling_map to be provided." + "SabrePreLayout requires coupling_map to be used with either" + "CouplingMap or a Target." ) starting_layout = None diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 1e7aa91f0b8f..abc48f63ae68 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -28,6 +28,7 @@ from qiskit.providers.fake_provider import FakeKolkata from qiskit.providers.fake_provider import FakeMontreal from qiskit.transpiler.passes.layout.sabre_pre_layout import SabrePreLayout +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager class TestSabreLayout(QiskitTestCase): @@ -419,6 +420,18 @@ def test_starting_layout(self): [30, 98, 104, 36, 103, 35, 65, 28, 61, 91, 22, 92, 23, 93, 62, 99], ) + def test_integration_with_pass_manager(self): + """Tests SabrePreLayoutIntegration with the rest of PassManager pipeline.""" + backend = FakeAlmadenV2() + pm = generate_preset_pass_manager(1, backend, seed_transpiler=0) + pm.pre_layout = PassManager([SabrePreLayout(backend.target)]) + qct = pm.run(self.circuit) + qct_initial_layout = qct.layout.initial_layout + self.assertEqual( + [qct_initial_layout[q] for q in self.circuit.qubits], + [1, 6, 5, 10, 11, 12, 16, 17, 18, 13, 14, 9, 8, 3, 2, 0], + ) + if __name__ == "__main__": unittest.main() From 70754a5223914f21c1e0cc0f1ce86623c7f2c6e0 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Mon, 16 Oct 2023 16:16:57 +0300 Subject: [PATCH 16/16] Update qiskit/transpiler/passes/layout/sabre_pre_layout.py Co-authored-by: Matthew Treinish --- qiskit/transpiler/passes/layout/sabre_pre_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/layout/sabre_pre_layout.py b/qiskit/transpiler/passes/layout/sabre_pre_layout.py index 7d666ee3edf9..55748fef44ee 100644 --- a/qiskit/transpiler/passes/layout/sabre_pre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_pre_layout.py @@ -136,7 +136,7 @@ def _add_extra_edges(self, distance): apart in the original graph. The extra edges are assigned errors allowing VF2 to prioritize real edges over extra edges. """ - nq = len(self.coupling_map.graph.node_indices()) + nq = len(self.coupling_map.graph) augmented_coupling_map = CouplingMap() augmented_coupling_map.graph = self.coupling_map.graph.copy() augmented_error_map = ErrorMap(nq)