From 42a0ee84df3e15834b174bdb0295142b987b261f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 24 Jul 2023 17:18:58 -0400 Subject: [PATCH] Fix final_layout when VF2PostLayout finds a better layout (#10466) * Fix final_layout when VF2PostLayout finds a better layout This commit fixes a bug in the preset pass managers when VF2PostLayout is run and finds a better layout to use. In these cases the ApplyLayout was updating the layout but we never updated the final layout to reflect these changes. This would result in an incorrect final layout because the input positions of the qubits were incorrect after re-applying the layout. This commit fixes this by adding code to ApplyLayout to also update final_layout, if one is set, to reflect the new initial layout found by VF2PostLayout. Fixes #10457 * Remove stray debug print * Use a list instead of a dict * Add assertion on vf2postlayout being used in compiler.transpile tests * Actually assert a post layout is set --- .../transpiler/passes/layout/apply_layout.py | 9 ++++ ...yout-in-apply-layout-dfbdbde593cf7929.yaml | 15 ++++++ test/python/compiler/test_transpiler.py | 42 ++++++++++++++- test/python/transpiler/test_apply_layout.py | 53 ++++++++++++++++++- 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index 2f9305b23244..a69d90f22163 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -83,9 +83,11 @@ def run(self, dag): full_layout = Layout() old_phys_to_virtual = layout.get_physical_bits() new_virtual_to_physical = post_layout.get_virtual_bits() + phys_map = list(range(len(new_dag.qubits))) for new_virt, new_phys in new_virtual_to_physical.items(): old_phys = dag.find_bit(new_virt).index old_virt = old_phys_to_virtual[old_phys] + phys_map[old_phys] = new_phys full_layout.add(old_virt, new_phys) for reg in layout.get_registers(): full_layout.add_register(reg) @@ -94,6 +96,13 @@ def run(self, dag): qargs = [q[new_virtual_to_physical[qarg]] for qarg in node.qargs] new_dag.apply_operation_back(node.op, qargs, node.cargs) self.property_set["layout"] = full_layout + if (final_layout := self.property_set["final_layout"]) is not None: + final_layout_mapping = { + new_dag.qubits[phys_map[dag.find_bit(old_virt).index]]: phys_map[old_phys] + for old_virt, old_phys in final_layout.get_virtual_bits().items() + } + out_layout = Layout(final_layout_mapping) + self.property_set["final_layout"] = out_layout new_dag._global_phase = dag._global_phase return new_dag diff --git a/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml b/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml new file mode 100644 index 000000000000..f4297f55d8a7 --- /dev/null +++ b/releasenotes/notes/fix-final-layout-in-apply-layout-dfbdbde593cf7929.yaml @@ -0,0 +1,15 @@ +--- +fixes: + - | + Fixed an issue with the :func:`~.transpile` function and all the preset + pass managers generated via :func:`~.generate_preset_pass_manager` where + the output :class:`~.QuantumCircuit` object's :attr:`~.QuantumCircuit.layout` + attribute would have an invalid :attr:`.TranspileLayout.final_layout` + attribute. This would occur in scenarios when the :class:`~.VF2PostLayout` + pass would run and find an alternative initial layout that has lower + reported error rates. When altering the initial layout the + :attr:`~.TranspileLayout.final_layout` attribute was never updated to + reflect this change. This has been corrected so that the ``final_layout`` + is always correctly reflecting the output permutation caused by the routing + stage. + Fixed `#10457 `__ diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 92187ec74999..614dad48722d 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -67,6 +67,7 @@ FakeNairobiV2, FakeRueschlikon, FakeSherbrooke, + FakeVigo, ) from qiskit.providers.options import Options from qiskit.pulse import InstructionScheduleMap @@ -75,7 +76,7 @@ from qiskit.tools import parallel from qiskit.transpiler import CouplingMap, Layout, PassManager, TransformationPass from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection +from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection, VF2PostLayout from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager, level_0_pass_manager from qiskit.transpiler.target import InstructionProperties, Target @@ -1857,6 +1858,45 @@ def test_transpile_target_no_measurement_error(self, opt_level): res = transpile(qc, target=target, optimization_level=opt_level) self.assertEqual(qc, res) + def test_transpile_final_layout_updated_with_post_layout(self): + """Test that the final layout is correctly set when vf2postlayout runs. + + Reproduce from #10457 + """ + + def _get_index_layout(transpiled_circuit: QuantumCircuit, num_source_qubits: int): + """Return the index layout of a transpiled circuit""" + layout = transpiled_circuit.layout + if layout is None: + return list(range(num_source_qubits)) + + pos_to_virt = {v: k for k, v in layout.input_qubit_mapping.items()} + qubit_indices = [] + for index in range(num_source_qubits): + qubit_idx = layout.initial_layout[pos_to_virt[index]] + if layout.final_layout is not None: + qubit_idx = layout.final_layout[transpiled_circuit.qubits[qubit_idx]] + qubit_indices.append(qubit_idx) + return qubit_indices + + vf2_post_layout_called = False + + def callback(**kwargs): + nonlocal vf2_post_layout_called + if isinstance(kwargs["pass_"], VF2PostLayout): + vf2_post_layout_called = True + self.assertIsNotNone(kwargs["property_set"]["post_layout"]) + + backend = FakeVigo() + qubits = 3 + qc = QuantumCircuit(qubits) + for i in range(5): + qc.cx(i % qubits, int(i + qubits / 2) % qubits) + + tqc = transpile(qc, backend=backend, seed_transpiler=4242, callback=callback) + self.assertTrue(vf2_post_layout_called) + self.assertEqual([3, 2, 1], _get_index_layout(tqc, qubits)) + class StreamHandlerRaiseException(StreamHandler): """Handler class that will raise an exception on formatting errors.""" diff --git a/test/python/transpiler/test_apply_layout.py b/test/python/transpiler/test_apply_layout.py index ae950912840d..d156db19dc18 100644 --- a/test/python/transpiler/test_apply_layout.py +++ b/test/python/transpiler/test_apply_layout.py @@ -18,8 +18,11 @@ from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.transpiler.layout import Layout -from qiskit.transpiler.passes import ApplyLayout +from qiskit.transpiler.passes import ApplyLayout, SetLayout from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.preset_passmanagers import common +from qiskit.providers.fake_provider import FakeVigoV2 +from qiskit.transpiler import PassManager class TestApplyLayout(QiskitTestCase): @@ -115,6 +118,54 @@ def test_circuit_with_swap_gate(self): self.assertEqual(circuit_to_dag(expected), after) + def test_final_layout_is_updated(self): + """Test that if vf2postlayout runs that we've updated the final layout.""" + qubits = 3 + qc = QuantumCircuit(qubits) + for i in range(5): + qc.cx(i % qubits, int(i + qubits / 2) % qubits) + initial_pm = PassManager([SetLayout([1, 3, 4])]) + cmap = FakeVigoV2().coupling_map + initial_pm += common.generate_embed_passmanager(cmap) + first_layout_circ = initial_pm.run(qc) + out_pass = ApplyLayout() + out_pass.property_set["layout"] = first_layout_circ.layout.initial_layout + out_pass.property_set[ + "original_qubit_indices" + ] = first_layout_circ.layout.input_qubit_mapping + out_pass.property_set["final_layout"] = Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[1]: 3, + first_layout_circ.qubits[2]: 2, + first_layout_circ.qubits[3]: 4, + first_layout_circ.qubits[4]: 1, + } + ) + # Set a post layout like vf2postlayout would: + out_pass.property_set["post_layout"] = Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[2]: 4, + first_layout_circ.qubits[1]: 2, + first_layout_circ.qubits[3]: 1, + first_layout_circ.qubits[4]: 3, + } + ) + out_pass(first_layout_circ) + self.assertEqual( + out_pass.property_set["final_layout"], + Layout( + { + first_layout_circ.qubits[0]: 0, + first_layout_circ.qubits[2]: 1, + first_layout_circ.qubits[4]: 4, + first_layout_circ.qubits[1]: 3, + first_layout_circ.qubits[3]: 2, + } + ), + ) + if __name__ == "__main__": unittest.main()