From b2c9f7008e6697d72ac3bc7639bd9f6540086841 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 4 Mar 2022 07:45:23 -0500 Subject: [PATCH] Make VF2Layout pass Target aware This commit updates the VF2Layout pass to be target aware. It adds a new kwarg for specifying a target and if it's specified that target object is used to define the parameters for finding the isomorphic mapping used for the layout. However, with this commit the extra information contained in the target isn't really leveraged yet and just the global coupling graph and measurement error rates are pulled from the target just as in the BackendV1 case. This commit is mostly to facilitate future expansion where we will improve the layout scoring heuristic used and having the full set of data available in the target will be useful. Part of #7113 --- qiskit/transpiler/passes/layout/vf2_layout.py | 62 +++++++++++++------ .../transpiler/preset_passmanagers/level1.py | 1 + .../transpiler/preset_passmanagers/level2.py | 1 + .../transpiler/preset_passmanagers/level3.py | 1 + test/python/transpiler/test_vf2_layout.py | 23 ++++++- 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py index fb4d95718a1b..81e0ef5f9b14 100644 --- a/qiskit/transpiler/passes/layout/vf2_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_layout.py @@ -54,13 +54,14 @@ class VF2Layout(AnalysisPass): def __init__( self, - coupling_map, + coupling_map=None, strict_direction=False, seed=None, call_limit=None, time_limit=None, properties=None, max_trials=None, + target=None, ): """Initialize a ``VF2Layout`` pass instance @@ -80,14 +81,26 @@ def __init__( based on the number of edges in the interaction graph or the coupling graph (whichever is larger). If set to a value <= 0 no limit on the number of trials will be set. + target (Target): A target representing the backend device to run vf2layout on. + If specified it will supersede a set value for ``properties`` and + ``coupling_map``. + + Raises: + TypeError: If neither ``coupling_map`` or ``target`` are provided. """ super().__init__() - self.coupling_map = coupling_map + self.target = target + if target is not None: + self.coupling_map = self.target.build_coupling_map() + elif coupling_map is not None: + self.coupling_map = coupling_map + else: + raise TypeError("coupling_map or target must be specified.") + self.properties = properties self.strict_direction = strict_direction self.seed = seed self.call_limit = call_limit self.time_limit = time_limit - self.properties = properties self.max_trials = max_trials def run(self, dag): @@ -197,20 +210,33 @@ def _score_layout(self, layout): to weight against higher connectivity qubits.""" bits = layout.get_physical_bits() score = 0 - if self.properties is None: - # Sum qubit degree for each qubit in chosen layout as really rough estimate of error + if self.target: + if "measure" in self.target: + for bit in bits: + props = self.target["measure"].get((bit,)) + if props is None or props.error is None: + score += ( + self.coupling_map.graph.out_degree(bit) + + self.coupling_map.graph.in_degree(bit) + ) / len(self.coupling_map.graph) + else: + score += props.error + else: + if self.properties is None: + # Sum qubit degree for each qubit in chosen layout as really rough estimate of error + for bit in bits: + score += self.coupling_map.graph.out_degree( + bit + ) + self.coupling_map.graph.in_degree(bit) + return score for bit in bits: - score += self.coupling_map.graph.out_degree( - bit - ) + self.coupling_map.graph.in_degree(bit) - return score - for bit in bits: - try: - score += self.properties.readout_error(bit) - # If readout error can't be found in properties fallback to degree - # divided by number of qubits as a terrible approximation - except BackendPropertyError: - score += ( - self.coupling_map.graph.out_degree(bit) + self.coupling_map.graph.in_degree(bit) - ) / len(self.coupling_map.graph) + try: + score += self.properties.readout_error(bit) + # If readout error can't be found in properties fallback to degree + # divided by number of qubits as a terrible approximation + except BackendPropertyError: + score += ( + self.coupling_map.graph.out_degree(bit) + + self.coupling_map.graph.in_degree(bit) + ) / len(self.coupling_map.graph) return score diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 86d2de27cb9f..4c966ead5a4b 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -153,6 +153,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2 time_limit=0.1, properties=backend_properties, + target=target, ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index a091b992d851..16ed4b7ab44c 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -150,6 +150,7 @@ def _vf2_match_not_found(property_set): call_limit=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2 time_limit=10.0, properties=backend_properties, + target=target, ) ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index d8e6a70448ba..91af4cf7d9e8 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -153,6 +153,7 @@ def _vf2_match_not_found(property_set): call_limit=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2 time_limit=60, properties=backend_properties, + target=target, ) ) # 2b. if VF2 didn't converge on a solution use layout_method (dense). diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py index f39ea8bf8346..1ee1b0f6f874 100644 --- a/test/python/transpiler/test_vf2_layout.py +++ b/test/python/transpiler/test_vf2_layout.py @@ -17,12 +17,12 @@ import retworkx from qiskit import QuantumRegister, QuantumCircuit -from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout, Target from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout, VF2LayoutStopReason from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeTenerife, FakeRueschlikon, FakeManhattan, FakeYorktown -from qiskit.circuit.library import GraphState +from qiskit.circuit.library import GraphState, CXGate class LayoutTestCase(QiskitTestCase): @@ -119,6 +119,25 @@ def test_call_limit(self): pass_.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.NO_SOLUTION_FOUND ) + def test_coupling_map_and_target(self): + """Test that a Target is used instead of a CouplingMap if both are specified.""" + cmap = CouplingMap([[0, 1], [1, 2], [2, 0]]) + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None, (1, 0): None}) + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) # qr0-> qr1 + circuit.cx(qr[1], qr[2]) # qr1-> qr2 + dag = circuit_to_dag(circuit) + pass_ = VF2Layout(cmap, seed=-1, max_trials=1, target=target) + pass_.run(dag) + self.assertLayout(dag, target.build_coupling_map(), pass_.property_set) + + def test_neither_coupling_map_or_target(self): + """Test that we raise if neither a target or coupling map is specified.""" + with self.assertRaises(TypeError): + VF2Layout(seed=123, call_limit=1000, time_limit=20, max_trials=7) + class TestVF2LayoutLattice(LayoutTestCase): """Fit in 25x25 hexagonal lattice coupling map"""