Skip to content

Commit

Permalink
Fix final_layout when VF2PostLayout finds a better layout (#10466)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mtreinish authored Jul 24, 2023
1 parent 6143c3f commit 42a0ee8
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
9 changes: 9 additions & 0 deletions qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/qiskit-terra/issues/10457>`__
42 changes: 41 additions & 1 deletion test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
FakeNairobiV2,
FakeRueschlikon,
FakeSherbrooke,
FakeVigo,
)
from qiskit.providers.options import Options
from qiskit.pulse import InstructionScheduleMap
Expand All @@ -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
Expand Down Expand Up @@ -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."""
Expand Down
53 changes: 52 additions & 1 deletion test/python/transpiler/test_apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()

0 comments on commit 42a0ee8

Please sign in to comment.