Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix final_layout when VF2PostLayout finds a better layout #10466

Merged
merged 7 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

et tu?

Pacific_Walrus_-Bull(8247646168)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I was wondering if you'd notice, figured I'd give it a try :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apparently didn't notice it the first time I read it, but saw it when I was checking the changes haha.

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,
}
),
)
jakelishman marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
unittest.main()