Skip to content
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
5 changes: 3 additions & 2 deletions qiskit/transpiler/passes/layout/vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ def run(self, dag):
self.property_set["VF2Layout_stop_reason"] = VF2LayoutStopReason.SOLUTION_FOUND
mapping = {dag.qubits[virt]: phys for virt, phys in layout.items()}
chosen_layout = Layout(mapping)
self.property_set["layout"] = chosen_layout

self.property_set["layout"] = vf2_utils.allocate_idle_qubits(dag, target, chosen_layout)
for reg in dag.qregs.values():
self.property_set["layout"].add_register(reg)
return
Expand Down Expand Up @@ -285,7 +286,7 @@ def mapping_to_layout(layout_mapping):
if chosen_layout is None:
self.property_set["VF2Layout_stop_reason"] = VF2LayoutStopReason.NO_SOLUTION_FOUND
return
self.property_set["layout"] = chosen_layout
self.property_set["layout"] = vf2_utils.allocate_idle_qubits(dag, target, chosen_layout)
for reg in dag.qregs.values():
self.property_set["layout"].add_register(reg)

Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/passes/layout/vf2_post_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ def run(self, dag):
used_bits.add(i)
chosen_layout.add(bit, i)
break
self.property_set["post_layout"] = chosen_layout
self.property_set["post_layout"] = vf2_utils.allocate_idle_qubits(
dag, self.target, chosen_layout
)
else:
if chosen_layout is None:
stop_reason = VF2PostLayoutStopReason.NO_SOLUTION_FOUND
Expand Down
11 changes: 11 additions & 0 deletions qiskit/transpiler/passes/layout/vf2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@
from qiskit._accelerate.error_map import ErrorMap


def allocate_idle_qubits(dag, target, layout):
"""Allocate the idle virtual qubits in the input DAG to arbitrary physical qubits."""
# Extend with arbitrary decisions for idle qubits.
used_physical = set(layout.get_physical_bits())
unused_physicals = (q for q in range(target.num_qubits) if q not in used_physical)
for bit in dag.qubits:
if bit not in layout:
layout[bit] = next(unused_physicals)
return layout


def build_interaction_graph(dag, strict_direction=True):
"""Build an interaction graph from a dag."""
im_graph = PyDiGraph(multigraph=False) if strict_direction else PyGraph(multigraph=False)
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/vf2-idle-qubits-65d8875b2fb67fe1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
:class:`.VF2Layout` and :class:`.VF2PostLayout` will now correctly include (arbitrary) layout
assignments for completely idle qubits. Previously this might have been observed by calls to
:meth:`.TranspileLayout.initial_index_layout` failing after a compilation.
23 changes: 21 additions & 2 deletions test/python/transpiler/test_vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import rustworkx

from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister
from qiskit.circuit import ControlFlowOp
from qiskit.circuit import ControlFlowOp, Qubit
from qiskit.transpiler import CouplingMap, Target, TranspilerError
from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout, VF2LayoutStopReason
from qiskit._accelerate.error_map import ErrorMap
Expand All @@ -33,7 +33,7 @@
from qiskit.transpiler import PassManager, AnalysisPass
from qiskit.transpiler.target import InstructionProperties
from qiskit.transpiler.preset_passmanagers.common import generate_embed_passmanager
from test import QiskitTestCase # pylint: disable=wrong-import-order
from test import QiskitTestCase, combine # pylint: disable=wrong-import-order

from ..legacy_cmaps import TENERIFE_CMAP, RUESCHLIKON_CMAP, MANHATTAN_CMAP, YORKTOWN_CMAP

Expand Down Expand Up @@ -307,6 +307,25 @@ def test_determinism_all_1q(self):
layouts[0], layout, f"Layout for execution {i} differs from the expected"
)

@combine(
seed=(-1, 12), # This hits both the "seeded" and "unseeded" paths.
strict_direction=(True, False),
)
def test_complete_layout_with_idle_qubits(self, seed, strict_direction):
"""Test that completely idle qubits are included in the resulting layout."""
# Use registerless qubits to avoid any register-based shenangigans from adding the bits
# automatically.
qc = QuantumCircuit([Qubit() for _ in range(3)])
qc.cx(0, 1)
target = Target.from_configuration(
num_qubits=3, basis_gates=["sx", "rz", "cx"], coupling_map=CouplingMap.from_line(3)
)
property_set = {}
pass_ = VF2Layout(target=target, seed=seed, strict_direction=strict_direction)
pass_(qc, property_set=property_set)
unallocated = {i for i, bit in enumerate(qc.qubits) if bit not in property_set["layout"]}
self.assertEqual(unallocated, set())


@ddt.ddt
class TestVF2LayoutLattice(LayoutTestCase):
Expand Down
30 changes: 29 additions & 1 deletion test/python/transpiler/test_vf2_post_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"""Test the VF2Layout pass"""

import ddt
import rustworkx

from qiskit import QuantumRegister, QuantumCircuit
Expand All @@ -24,11 +25,12 @@
from qiskit.circuit import Qubit
from qiskit.compiler.transpiler import transpile
from qiskit.transpiler.target import Target, InstructionProperties
from test import QiskitTestCase # pylint: disable=wrong-import-order
from test import QiskitTestCase, combine # pylint: disable=wrong-import-order

from ..legacy_cmaps import LIMA_CMAP, YORKTOWN_CMAP, BOGOTA_CMAP


@ddt.ddt
class TestVF2PostLayout(QiskitTestCase):
"""Tests the VF2Layout pass"""

Expand Down Expand Up @@ -324,6 +326,32 @@ def test_last_qubits_best(self):
vf2_pass.run(dag)
self.assertLayoutV2(dag, target_last_qubits_best, vf2_pass.property_set)

@combine(
seed=(-1, 12), # This hits both the "seeded" and "unseeded" paths.
strict_direction=(True, False),
)
def test_complete_layout_with_idle_qubits(self, seed, strict_direction):
"""Test that completely idle qubits are included in the resulting layout."""
qc = QuantumCircuit(3)
qc.cx(0, 1)
# We need to ensure that VF2Post actually triggers a remapping.
target = Target(3)
target.add_instruction(
CXGate(),
properties={
(0, 1): InstructionProperties(error=1e-1),
(1, 0): InstructionProperties(error=1e-1),
(2, 1): InstructionProperties(error=1e-8),
},
)
property_set = {}
pass_ = VF2PostLayout(target=target, seed=seed, strict_direction=strict_direction)
pass_(qc, property_set=property_set)
unallocated = {
i for i, bit in enumerate(qc.qubits) if bit not in property_set["post_layout"]
}
self.assertEqual(unallocated, set())


class TestVF2PostLayoutScoring(QiskitTestCase):
"""Test scoring heuristic function for VF2PostLayout."""
Expand Down
Loading