Skip to content

Fix tracking of routing permutation in Sabre with disjoint backends #13833

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

Merged
merged 1 commit into from
Feb 13, 2025
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
13 changes: 13 additions & 0 deletions qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ def run(self, dag):
for initial, final in enumerate(component.final_permutation)
}
)

# The coupling map may have been split into more components than the DAG. In this case,
# there will be some physical qubits unaccounted for in our `final_layout`. Strictly the
# `if` check is unnecessary, but we can avoid the loop for most circuits and backends.
if len(final_layout) != len(physical_qubits):
used_qubits = {
qubit for component in components for qubit in component.coupling_map.graph.nodes()
}
for index, qubit in enumerate(physical_qubits):
if index in used_qubits:
continue
final_layout[qubit] = index

if self.property_set["final_layout"] is None:
self.property_set["final_layout"] = final_layout
else:
Expand Down
15 changes: 15 additions & 0 deletions releasenotes/notes/sabre-disjoint-routing-85c6f6481c9ffca4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
fixes:
- |
When :class:`.SabreLayout` is used to do both layout and routing simultaneously (as is the case
for the default options to :func:`.transpile` and :func:`.generate_preset_pass_manager`) on a
:class:`.Target` or :class:`.CouplingMap` with disjoint connectivity, and the input circuit fits
into a single component of the coupling map, the routing permutation will now be tracked
correctly.

Previously, any qubits in the coupling map that were not connected, even indirectly, to a qubit
used by the routed circuit would not be included in the final routing permutation. This could
cause surprising behaviour a long way from the point of failure, even if compilation appeared to
succeed, such as calls to :meth:`.TranspileLayout.final_index_layout` raising :exc:`KeyError`.

This bug did not affect backends that were fully connected, as most are.
15 changes: 15 additions & 0 deletions test/python/transpiler/test_sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,21 @@ def test_with_partial_layout(self):
layout = pm.property_set["layout"]
self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])

def test_dag_fits_in_one_component(self):
"""Test that the output is valid if the DAG all fits in a single component of a disjoint
coupling map.."""
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 0)

disjoint = CouplingMap([(0, 1), (1, 2), (3, 4), (4, 5)])
layout_routing_pass = SabreLayout(disjoint, seed=2025_02_12, swap_trials=1, layout_trials=1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Very precise seed

out = layout_routing_pass(qc)
self.assertEqual(len(out.layout.initial_layout), len(out.layout.final_layout))
self.assertEqual(out.layout.initial_index_layout(filter_ancillas=False), [1, 0, 2, 3, 4, 5])
self.assertEqual(out.layout.final_index_layout(filter_ancillas=False), [2, 0, 1, 3, 4, 5])


class TestSabrePreLayout(QiskitTestCase):
"""Tests the SabreLayout pass with starting layout created by SabrePreLayout."""
Expand Down