From 6580d9687911eaa29ca8633f635eba18f2b38417 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 25 Oct 2022 17:51:24 -0400 Subject: [PATCH] Fix VF2Layout and VF2PostLayout handling of instructions without error (#8978) * Fix VF2Layout and VF2PostLayout handling of instructions without error This commit fixes an issue in the VF2Layout and VF2PostLayout pass where the passes would potentially error in cases when a target was specified and there were instructions present without any error rates defined. In such cases the instructions should be treated as ideal (having no error) and the passes shouldn't fail. In cases where there are no error rates in the target for VF2Layout the first perfect match should be used, and for VF2PostLayout it should effectively be a no-op and not select a new layout. Fixes #8970 * Add missing comma to comment * Exit early if score is 0 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/passes/layout/vf2_layout.py | 11 +++++ .../passes/layout/vf2_post_layout.py | 46 ++++++++++++++++--- qiskit/transpiler/passes/layout/vf2_utils.py | 7 ++- ...-vf2-layout-no-noise-22261601684710c3.yaml | 15 ++++++ test/python/transpiler/test_vf2_layout.py | 31 ++++++++++++- .../python/transpiler/test_vf2_post_layout.py | 31 +++++++++++++ 6 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/fix-vf2-layout-no-noise-22261601684710c3.yaml diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py index 7ae14c01c1..b20ca0cf9a 100644 --- a/qiskit/transpiler/passes/layout/vf2_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_layout.py @@ -154,6 +154,11 @@ class VF2Layout(AnalysisPass): if len(cm_graph) == len(im_graph): chosen_layout = layout break + # If there is no error map avilable we can just skip the scoring stage as there + # is nothing to score with, so any match is the best we can find. + if not self.avg_error_map: + chosen_layout = layout + break layout_score = vf2_utils.score_layout( self.avg_error_map, layout, @@ -162,6 +167,12 @@ class VF2Layout(AnalysisPass): im_graph, self.strict_direction, ) + # If the layout score is 0 we can't do any better and we'll just + # waste time finding additional mappings that will at best match + # the performance, so exit early in this case + if layout_score == 0.0: + chosen_layout = layout + break logger.debug("Trial %s has score %s", trials, layout_score) if chosen_layout is None: chosen_layout = layout diff --git a/qiskit/transpiler/passes/layout/vf2_post_layout.py b/qiskit/transpiler/passes/layout/vf2_post_layout.py index fb7387f8bb..01576adc89 100644 --- a/qiskit/transpiler/passes/layout/vf2_post_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_post_layout.py @@ -13,6 +13,7 @@ """VF2PostLayout pass to find a layout after transpile using subgraph isomorphism""" from enum import Enum import logging +import inspect import time from retworkx import PyDiGraph, vf2_mapping, PyGraph @@ -131,6 +132,7 @@ class VF2PostLayout(AnalysisPass): self.avg_error_map = vf2_utils.build_average_error_map( self.target, self.properties, self.coupling_map ) + result = vf2_utils.build_interaction_graph(dag, self.strict_direction) if result is None: self.property_set["VF2PostLayout_stop_reason"] = VF2PostLayoutStopReason.MORE_THAN_2Q @@ -138,21 +140,53 @@ class VF2PostLayout(AnalysisPass): im_graph, im_graph_node_map, reverse_im_graph_node_map = result if self.target is not None: + # If qargs is None then target is global and ideal so no + # scoring is needed + if self.target.qargs is None: + return if self.strict_direction: cm_graph = PyDiGraph(multigraph=False) else: cm_graph = PyGraph(multigraph=False) - cm_graph.add_nodes_from( - [self.target.operation_names_for_qargs((i,)) for i in range(self.target.num_qubits)] - ) + # If None is present in qargs there are globally defined ideal operations + # we should add these to all entries based on the number of qubits so we + # treat that as a valid operation even if there is no scoring for the + # strict direction case + global_ops = None + if None in self.target.qargs: + global_ops = {1: [], 2: []} + for op in self.target.operation_names_for_qargs(None): + operation = self.target.operation_for_name(op) + # If operation is a class this is a variable width ideal instruction + # so we treat it as available on both 1 and 2 qubits + if inspect.isclass(operation): + global_ops[1].append(op) + global_ops[2].append(op) + else: + num_qubits = operation.num_qubits + if num_qubits in global_ops: + global_ops[num_qubits].append(op) + op_names = [] + for i in range(self.target.num_qubits): + entry = set() + try: + entry = set(self.target.operation_names_for_qargs((i,))) + except KeyError: + pass + if global_ops is not None: + entry.update(global_ops[1]) + op_names.append(entry) + + cm_graph.add_nodes_from(op_names) for qargs in self.target.qargs: len_args = len(qargs) # If qargs == 1 we already populated it and if qargs > 2 there are no instructions # using those in the circuit because we'd have already returned by this point if len_args == 2: - cm_graph.add_edge( - qargs[0], qargs[1], self.target.operation_names_for_qargs(qargs) - ) + ops = set(self.target.operation_names_for_qargs(qargs)) + if global_ops is not None: + ops.update(global_ops[2]) + cm_graph.add_edge(qargs[0], qargs[1], ops) cm_nodes = list(cm_graph.node_indexes()) else: cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph( diff --git a/qiskit/transpiler/passes/layout/vf2_utils.py b/qiskit/transpiler/passes/layout/vf2_utils.py index 6a458a4cdc..950b2447d9 100644 --- a/qiskit/transpiler/passes/layout/vf2_utils.py +++ b/qiskit/transpiler/passes/layout/vf2_utils.py @@ -84,13 +84,16 @@ def score_layout(avg_error_map, layout, bit_map, reverse_bit_map, im_graph, stri fidelity = 1 for bit, node_index in bit_map.items(): gate_count = sum(im_graph[node_index].values()) - fidelity *= (1 - avg_error_map[(bits[bit],)]) ** gate_count + error_rate = avg_error_map.get((bits[bit],)) + if error_rate is not None: + fidelity *= (1 - avg_error_map[(bits[bit],)]) ** gate_count for edge in im_graph.edge_index_map().values(): gate_count = sum(edge[2].values()) qargs = (bits[reverse_bit_map[edge[0]]], bits[reverse_bit_map[edge[1]]]) if not strict_direction and qargs not in avg_error_map: qargs = (qargs[1], qargs[0]) - fidelity *= (1 - avg_error_map[qargs]) ** gate_count + if qargs in avg_error_map: + fidelity *= (1 - avg_error_map[qargs]) ** gate_count return 1 - fidelity diff --git a/releasenotes/notes/fix-vf2-layout-no-noise-22261601684710c3.yaml b/releasenotes/notes/fix-vf2-layout-no-noise-22261601684710c3.yaml new file mode 100644 index 0000000000..9a685430c7 --- /dev/null +++ b/releasenotes/notes/fix-vf2-layout-no-noise-22261601684710c3.yaml @@ -0,0 +1,15 @@ +--- +fixes: + - | + Fixed an issue with the :class:`~.VF2Layout` pass where it would error + when running with a :class:`~.Target` that had instructions that were + missing error rates. This has been corrected so in such cases the + lack of an error rate will be treated as an ideal implementation and + if no error rates are present it will just select the first matching + layout. + Fixed `#8970 `__ + - | + Fixed an issue with the :class:`~.VF2PostLayout` pass where it would + error when running with a :class:~.Target` that had instructions that + were missing. In such cases the lack of an error rate will be treated as + an ideal implementation of the operation. diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py index 8965a51917..15746e2d36 100644 --- a/test/python/transpiler/test_vf2_layout.py +++ b/test/python/transpiler/test_vf2_layout.py @@ -32,8 +32,9 @@ from qiskit.providers.fake_provider import ( FakeYorktown, FakeGuadalupeV2, ) -from qiskit.circuit.library import GraphState, CXGate +from qiskit.circuit.library import GraphState, CXGate, XGate from qiskit.transpiler import PassManager +from qiskit.transpiler.target import InstructionProperties from qiskit.transpiler.preset_passmanagers.common import generate_embed_passmanager @@ -217,6 +218,34 @@ class TestVF2LayoutSimple(LayoutTestCase): with self.assertRaises(TranspilerError): vf2_pass.run(dag) + def test_target_no_error(self): + """Test that running vf2layout on a pass against a target with no error rates works.""" + n_qubits = 15 + target = Target() + target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)}) + vf2_pass = VF2Layout(target=target) + circuit = QuantumCircuit(2) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + self.assertLayout(dag, target.build_coupling_map(), vf2_pass.property_set) + + def test_target_some_error(self): + """Test that running vf2layout on a pass against a target with some error rates works.""" + n_qubits = 15 + target = Target() + target.add_instruction( + XGate(), {(i,): InstructionProperties(error=0.00123) for i in range(n_qubits)} + ) + target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)}) + vf2_pass = VF2Layout(target=target) + circuit = QuantumCircuit(2) + circuit.h(0) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + self.assertLayout(dag, target.build_coupling_map(), vf2_pass.property_set) + class TestVF2LayoutLattice(LayoutTestCase): """Fit in 25x25 hexagonal lattice coupling map""" diff --git a/test/python/transpiler/test_vf2_post_layout.py b/test/python/transpiler/test_vf2_post_layout.py index 723bc6515c..c89dbd8ca7 100644 --- a/test/python/transpiler/test_vf2_post_layout.py +++ b/test/python/transpiler/test_vf2_post_layout.py @@ -16,6 +16,7 @@ import retworkx from qiskit import QuantumRegister, QuantumCircuit from qiskit.circuit import ControlFlowOp +from qiskit.circuit.library import CXGate, XGate from qiskit.transpiler import CouplingMap, Layout, TranspilerError from qiskit.transpiler.passes.layout import vf2_utils from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayout, VF2PostLayoutStopReason @@ -24,6 +25,7 @@ from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeLima, FakeYorktown, FakeLimaV2, FakeYorktownV2 from qiskit.circuit import Qubit from qiskit.compiler.transpiler import transpile +from qiskit.transpiler.target import Target, InstructionProperties class TestVF2PostLayout(QiskitTestCase): @@ -326,6 +328,35 @@ class TestVF2PostLayout(QiskitTestCase): VF2PostLayoutStopReason.NO_SOLUTION_FOUND, ) + def test_target_no_error(self): + """Test that running vf2layout on a pass against a target with no error rates works.""" + n_qubits = 15 + target = Target() + target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)}) + vf2_pass = VF2PostLayout(target=target) + circuit = QuantumCircuit(2) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + self.assertNotIn("post_layout", vf2_pass.property_set) + + def test_target_some_error(self): + """Test that running vf2layout on a pass against a target with some error rates works.""" + n_qubits = 15 + target = Target() + target.add_instruction( + XGate(), {(i,): InstructionProperties(error=0.00123) for i in range(n_qubits)} + ) + target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)}) + vf2_pass = VF2PostLayout(target=target, seed=1234, strict_direction=False) + circuit = QuantumCircuit(2) + circuit.h(0) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + vf2_pass.run(dag) + # No layout selected because nothing will beat initial layout + self.assertNotIn("post_layout", vf2_pass.property_set) + class TestVF2PostLayoutScoring(QiskitTestCase): """Test scoring heuristic function for VF2PostLayout."""