Skip to content

Commit

Permalink
Fix tracking of routing permutation in Sabre with disjoint backends (#…
Browse files Browse the repository at this point in the history
…13833)

If the backing coupling graph is disjoint, and unused components of the
coupling graph would not be considered when constructing the complete
routing permutation.  In practice, Sabre aborts immediately after layout
without attempting to route, if it needed to split the DAG across more
than one disjoint component, because it can't guarantee correctness of
the final routing in the presence of component-spanning barriers or
classical communication, so the only way for a component to be forgotten
is if the backend is disjoint, but the DAG fits into a single component.
  • Loading branch information
jakelishman authored Feb 13, 2025
1 parent 3eb3538 commit b933179
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 0 deletions.
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)
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

0 comments on commit b933179

Please sign in to comment.