Repository: Qiskit/qiskit

Issue:
Support control flow in Python components of `SabreLayout`
### What should we add?

Part of #9417.  Related to #9419.

## Details

As an overview, the `SabreLayout` algorithm is a relatively small wrapper around some externally defined routing pass.  Qiskit generally uses `SabreSwap` as this driver by default.  The layout-specific part of the algorithm is approximately:
1. choose a random initial layout
2. keep copies of the circuit, and the circuit with its operations reversed
3. run the routing pass to reach a final layout
4. run the routing pass on the reversed circuit, using the previous final layout as input.  Take the "final" layout from this routing of the reversed circuit to be the new initial layout of the forwards circuit.
5. repeat steps 3 and 4 several times, then use the resultant initial layout as the solution.

Our implementation of `SabreLayout` has two paths:

1. a pure-Python implementation that gets a routing pass injected during `SabreLayout` instance construction, then manually manages calling it with an internal `PassManager`.  This can handle any routing pass.
2. a preferred fast-path that immediately drops down to Rust space, and calls our Rust version of the Sabre routing algorithm repeatedly as the driver.  This lets us run several tries of the layout pass in parallel, significantly improving the output quality and run time.  This method acts as a joint layout+routing pass, since we can just reverse the final routed "reversed" circuit to get the output of the swap mapper without re-running it.

For initial support through `SabreLayout`, it is acceptable to only upgrade path 1.  Upgrading path 2 implies the more difficult, long-term direction of #9419 is taken, which can be done later once we understand more about how to route dynamic circuits.

_Technically_, `SabreLayout` should already execute on a dynamic circuit if a `StochasticSwap` instance is passed in to path 1 as the router.  However, this is logically not entirely correct; the "reverse circuit" operation will not recurse into the control-flow operations to reverse them, so the algorithm will likely produce very inefficient layouts, because part of its algorithm is stymied.

For the control-flow and classical operations currently implemented by Qiskit and supported by the other transpiler passes, implementing the reverse operation largely should just be a case of reversing the operations inside each control-flow block.  This is assuming that `break` and `continue` remain unsupported; these might have different properties.  Bear in mind that the "reverse" of the circuit doesn't have any real meaning in terms of circuit behaviour, it's just a tool we use as part of the algorithm; anything that logically lets us run the routing pass over the operations in the circuit in reverse topological order is fine.


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py
--- a/qiskit/transpiler/passes/layout/sabre_layout.py
+++ b/qiskit/transpiler/passes/layout/sabre_layout.py
@@ -34,7 +34,7 @@
     Heuristic,
     NeighborTable,
 )
-from qiskit.transpiler.passes.routing.sabre_swap import process_swaps, apply_gate
+from qiskit.transpiler.passes.routing.sabre_swap import _build_sabre_dag, _apply_sabre_result
 from qiskit.transpiler.target import Target
 from qiskit.transpiler.coupling import CouplingMap
 from qiskit.tools.parallel import CPU_COUNT
@@ -238,8 +238,8 @@ def run(self, dag):
             layout_dict,
             final_dict,
             component_map,
-            _gate_order,
-            _swap_map,
+            _sabre_result,
+            _circuit_to_dag_dict,
             local_dag,
         ) in layout_components:
             initial_layout_dict.update({k: component_map[v] for k, v in layout_dict.items()})
@@ -262,36 +262,25 @@ def run(self, dag):
             {dag.qubits[k]: v for (k, v) in final_layout_dict.items()}
         )
         canonical_register = dag.qregs["q"]
-        qubit_indices = {bit: idx for idx, bit in enumerate(canonical_register)}
         original_layout = NLayout.generate_trivial_layout(self.coupling_map.size())
         for (
             _layout_dict,
             _final_layout_dict,
             component_map,
-            gate_order,
-            swap_map,
+            sabre_result,
+            circuit_to_dag_dict,
             local_dag,
         ) in layout_components:
-            for node_id in gate_order:
-                node = local_dag._multi_graph[node_id]
-                process_swaps(
-                    swap_map,
-                    node,
-                    mapped_dag,
-                    original_layout,
-                    canonical_register,
-                    False,
-                    qubit_indices,
-                    component_map,
-                )
-                apply_gate(
-                    mapped_dag,
-                    node,
-                    original_layout,
-                    canonical_register,
-                    False,
-                    initial_layout_dict,
-                )
+            _apply_sabre_result(
+                mapped_dag,
+                local_dag,
+                initial_layout_dict,
+                canonical_register,
+                original_layout,
+                sabre_result,
+                circuit_to_dag_dict,
+                component_map,
+            )
         disjoint_utils.combine_barriers(mapped_dag, retain_uuid=False)
         return mapped_dag
 
@@ -305,25 +294,13 @@ def _inner_run(self, dag, coupling_map):
         neighbor_table = NeighborTable(rx.adjacency_matrix(coupling_map.graph))
         dist_matrix = coupling_map.distance_matrix
         original_qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
-        original_clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)}
-
-        dag_list = []
-        for node in dag.topological_op_nodes():
-            cargs = {original_clbit_indices[x] for x in node.cargs}
-            if node.op.condition is not None:
-                for clbit in dag._bits_in_operation(node.op):
-                    cargs.add(original_clbit_indices[clbit])
-
-            dag_list.append(
-                (
-                    node._node_id,
-                    [original_qubit_indices[x] for x in node.qargs],
-                    cargs,
-                )
-            )
-        ((initial_layout, final_layout), swap_map, gate_order) = sabre_layout_and_routing(
-            len(dag.clbits),
-            dag_list,
+        sabre_dag, circuit_to_dag_dict = _build_sabre_dag(
+            dag,
+            coupling_map.size(),
+            original_qubit_indices,
+        )
+        ((initial_layout, final_layout), sabre_result) = sabre_layout_and_routing(
+            sabre_dag,
             neighbor_table,
             dist_matrix,
             Heuristic.Decay,
@@ -332,6 +309,7 @@ def _inner_run(self, dag, coupling_map):
             self.layout_trials,
             self.seed,
         )
+
         # Apply initial layout selected.
         layout_dict = {}
         num_qubits = len(dag.qubits)
@@ -340,7 +318,14 @@ def _inner_run(self, dag, coupling_map):
                 layout_dict[dag.qubits[k]] = v
         final_layout_dict = final_layout.layout_mapping()
         component_mapping = {x: coupling_map.graph[x] for x in coupling_map.graph.node_indices()}
-        return layout_dict, final_layout_dict, component_mapping, gate_order, swap_map, dag
+        return (
+            layout_dict,
+            final_layout_dict,
+            component_mapping,
+            sabre_result,
+            circuit_to_dag_dict,
+            dag,
+        )
 
     def _apply_layout_no_pass_manager(self, dag):
         """Apply and embed a layout into a dagcircuit without using a ``PassManager`` to
diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py
--- a/qiskit/transpiler/passes/routing/sabre_swap.py
+++ b/qiskit/transpiler/passes/routing/sabre_swap.py
@@ -17,14 +17,16 @@
 
 import rustworkx
 
+from qiskit.circuit import ControlFlowOp
 from qiskit.circuit.library.standard_gates import SwapGate
+from qiskit.converters import dag_to_circuit
 from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.transpiler.coupling import CouplingMap
 from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.transpiler.layout import Layout
 from qiskit.transpiler.target import Target
 from qiskit.transpiler.passes.layout import disjoint_utils
-from qiskit.dagcircuit import DAGOpNode
+from qiskit.dagcircuit import DAGOpNode, DAGCircuit
 from qiskit.tools.parallel import CPU_COUNT
 
 from qiskit._accelerate.sabre_swap import (
@@ -221,11 +223,6 @@ def run(self, dag):
 
         self.dist_matrix = self.coupling_map.distance_matrix
 
-        # Preserve input DAG's name, regs, wire_map, etc. but replace the graph.
-        mapped_dag = None
-        if not self.fake_run:
-            mapped_dag = dag.copy_empty_like()
-
         canonical_register = dag.qregs["q"]
         current_layout = Layout.generate_trivial_layout(canonical_register)
         self._qubit_indices = {bit: idx for idx, bit in enumerate(canonical_register)}
@@ -236,22 +233,12 @@ def run(self, dag):
         layout = NLayout(layout_mapping, len(dag.qubits), self.coupling_map.size())
         original_layout = layout.copy()
 
-        dag_list = []
-        for node in dag.topological_op_nodes():
-            cargs = {self._clbit_indices[x] for x in node.cargs}
-            if node.op.condition is not None:
-                for clbit in dag._bits_in_operation(node.op):
-                    cargs.add(self._clbit_indices[clbit])
-
-            dag_list.append(
-                (
-                    node._node_id,
-                    [self._qubit_indices[x] for x in node.qargs],
-                    cargs,
-                )
-            )
-        sabre_dag = SabreDAG(len(dag.qubits), len(dag.clbits), dag_list)
-        swap_map, gate_order = build_swap_map(
+        sabre_dag, circuit_to_dag_dict = _build_sabre_dag(
+            dag,
+            self.coupling_map.size(),
+            self._qubit_indices,
+        )
+        sabre_result = build_swap_map(
             len(dag.qubits),
             sabre_dag,
             self._neighbor_table,
@@ -266,63 +253,197 @@ def run(self, dag):
         output_layout = Layout({dag.qubits[k]: v for (k, v) in layout_mapping})
         self.property_set["final_layout"] = output_layout
         if not self.fake_run:
-            for node_id in gate_order:
-                node = dag._multi_graph[node_id]
-                process_swaps(
-                    swap_map,
-                    node,
-                    mapped_dag,
-                    original_layout,
-                    canonical_register,
-                    self.fake_run,
-                    self._qubit_indices,
+            mapped_dag = dag.copy_empty_like()
+            _apply_sabre_result(
+                mapped_dag,
+                dag,
+                self._qubit_indices,
+                canonical_register,
+                original_layout,
+                sabre_result,
+                circuit_to_dag_dict,
+            )
+            return mapped_dag
+        return dag
+
+
+def _build_sabre_dag(dag, num_physical_qubits, qubit_indices):
+    from qiskit.converters import circuit_to_dag
+
+    # Maps id(block): circuit_to_dag(block) for all descendant blocks
+    circuit_to_dag_dict = {}
+
+    def recurse(block, block_qubit_indices):
+        block_id = id(block)
+        if block_id in circuit_to_dag_dict:
+            block_dag = circuit_to_dag_dict[block_id]
+        else:
+            block_dag = circuit_to_dag(block)
+            circuit_to_dag_dict[block_id] = block_dag
+        return process_dag(block_dag, block_qubit_indices)
+
+    def process_dag(block_dag, wire_map):
+        clbit_indices = {bit: idx for idx, bit in enumerate(block_dag.clbits)}
+        dag_list = []
+        node_blocks = {}
+        for node in block_dag.topological_op_nodes():
+            cargs = {clbit_indices[x] for x in node.cargs}
+            if node.op.condition is not None:
+                for clbit in block_dag._bits_in_operation(node.op):
+                    cargs.add(clbit_indices[clbit])
+            if isinstance(node.op, ControlFlowOp):
+                node_blocks[node._node_id] = [
+                    recurse(
+                        block,
+                        {inner: wire_map[outer] for inner, outer in zip(block.qubits, node.qargs)},
+                    )
+                    for block in node.op.blocks
+                ]
+            dag_list.append(
+                (
+                    node._node_id,
+                    [wire_map[x] for x in node.qargs],
+                    cargs,
                 )
-                apply_gate(
-                    mapped_dag,
-                    node,
-                    original_layout,
+            )
+        return SabreDAG(num_physical_qubits, block_dag.num_clbits(), dag_list, node_blocks)
+
+    return process_dag(dag, qubit_indices), circuit_to_dag_dict
+
+
+def _apply_sabre_result(
+    mapped_dag,
+    root_dag,
+    qubit_indices,
+    canonical_register,
+    initial_layout,
+    sabre_result,
+    circuit_to_dag_dict,
+    component_map=None,
+):
+    bit_to_qreg_idx = {bit: idx for idx, bit in enumerate(canonical_register)}
+
+    def empty_dag(node, block):
+        out = DAGCircuit()
+        for qreg in mapped_dag.qregs.values():
+            out.add_qreg(qreg)
+        out.add_clbits(node.cargs)
+        for creg in block.cregs:
+            out.add_creg(creg)
+        out._global_phase = block.global_phase
+        return out
+
+    def apply_inner(out_dag, current_layout, qubit_indices_inner, result, id_to_node):
+        for node_id in result.node_order:
+            node = id_to_node[node_id]
+            if isinstance(node.op, ControlFlowOp):
+                # Handle control flow op and continue.
+                block_results = result.node_block_results[node_id]
+                mapped_block_dags = []
+                idle_qubits = set(out_dag.qubits)
+                for block, block_result in zip(node.op.blocks, block_results):
+                    block_id_to_node = circuit_to_dag_dict[id(block)]._multi_graph
+                    mapped_block_dag = empty_dag(node, block)
+                    mapped_block_layout = current_layout.copy()
+                    block_qubit_indices = {
+                        inner: qubit_indices_inner[outer]
+                        for inner, outer in zip(block.qubits, node.qargs)
+                    }
+                    apply_inner(
+                        mapped_block_dag,
+                        mapped_block_layout,
+                        block_qubit_indices,
+                        block_result.result,
+                        block_id_to_node,
+                    )
+
+                    # Apply swap epilogue to bring each block to the same
+                    # final layout.
+                    process_swaps(
+                        block_result.swap_epilogue,
+                        mapped_block_dag,
+                        mapped_block_layout,
+                        canonical_register,
+                        False,
+                        bit_to_qreg_idx,
+                        component_map,
+                    )
+
+                    # If the swap epilogue didn't return us to the initial layout,
+                    # there's a bug.
+                    # assert mapped_block_layout.layout_mapping() == current_layout.layout_mapping()
+
+                    mapped_block_dags.append(mapped_block_dag)
+                    idle_qubits.intersection_update(mapped_block_dag.idle_wires())
+
+                mapped_blocks = []
+                for mapped_block_dag in mapped_block_dags:
+                    # Remove wires that are idle in all blocks.
+                    mapped_block_dag.remove_qubits(*idle_qubits)
+                    mapped_blocks.append(dag_to_circuit(mapped_block_dag))
+
+                # Apply the control flow gate to the dag.
+                mapped_node = node.op.replace_blocks(mapped_blocks)
+                mapped_node_qargs = mapped_blocks[0].qubits
+                out_dag.apply_operation_back(mapped_node, mapped_node_qargs, node.cargs)
+                continue
+
+            # If we get here, the node isn't a control-flow gate.
+            if node_id in result.map:
+                process_swaps(
+                    result.map[node_id],
+                    out_dag,
+                    current_layout,
                     canonical_register,
-                    self.fake_run,
-                    self._qubit_indices,
+                    False,
+                    bit_to_qreg_idx,
+                    component_map,
                 )
-            return mapped_dag
-        return dag
+            apply_gate(
+                out_dag,
+                node,
+                current_layout,
+                canonical_register,
+                False,
+                qubit_indices_inner,
+            )
+
+    apply_inner(mapped_dag, initial_layout, qubit_indices, sabre_result, root_dag._multi_graph)
 
 
 def process_swaps(
-    swap_map,
-    node,
+    swap_list,
     mapped_dag,
     current_layout,
     canonical_register,
     fake_run,
     qubit_indices,
-    swap_qubit_mapping=None,
+    component_map=None,
 ):
-    """Process swaps from SwapMap."""
-    if node._node_id in swap_map:
-        for swap in swap_map[node._node_id]:
-            if swap_qubit_mapping:
-                swap_qargs = [
-                    canonical_register[swap_qubit_mapping[swap[0]]],
-                    canonical_register[swap_qubit_mapping[swap[1]]],
-                ]
-            else:
-                swap_qargs = [canonical_register[swap[0]], canonical_register[swap[1]]]
-            apply_gate(
-                mapped_dag,
-                DAGOpNode(op=SwapGate(), qargs=swap_qargs),
-                current_layout,
-                canonical_register,
-                fake_run,
-                qubit_indices,
-            )
-            if swap_qubit_mapping:
-                current_layout.swap_logical(
-                    swap_qubit_mapping[swap[0]], swap_qubit_mapping[swap[1]]
-                )
-            else:
-                current_layout.swap_logical(*swap)
+    """
+    Applies each swap in ``swap_list`` sequentially and updates
+    ``current_layout`` accordingly.
+    """
+    for swap in swap_list:
+        if component_map:
+            swap_qargs = [
+                canonical_register[component_map[swap[0]]],
+                canonical_register[component_map[swap[1]]],
+            ]
+        else:
+            swap_qargs = [canonical_register[swap[0]], canonical_register[swap[1]]]
+        apply_gate(
+            mapped_dag,
+            DAGOpNode(op=SwapGate(), qargs=swap_qargs),
+            current_layout,
+            canonical_register,
+            fake_run,
+            qubit_indices,
+        )
+        if component_map:
+            current_layout.swap_logical(component_map[swap[0]], component_map[swap[1]])
+        else:
+            current_layout.swap_logical(*swap)
 
 
 def apply_gate(mapped_dag, node, current_layout, canonical_register, fake_run, qubit_indices):
diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py
--- a/qiskit/transpiler/preset_passmanagers/common.py
+++ b/qiskit/transpiler/preset_passmanagers/common.py
@@ -61,10 +61,10 @@
 # without error, since it is being supplied by a plugin and we don't have any knowledge of these.
 _CONTROL_FLOW_STATES = {
     "layout_method": _ControlFlowState(
-        working={"trivial", "dense"}, not_working={"sabre", "noise_adaptive"}
+        working={"trivial", "dense", "sabre"}, not_working={"noise_adaptive"}
     ),
     "routing_method": _ControlFlowState(
-        working={"none", "stochastic"}, not_working={"sabre", "lookahead", "basic"}
+        working={"none", "stochastic", "sabre"}, not_working={"lookahead", "basic"}
     ),
     # 'synthesis' is not a supported translation method because of the block-collection passes
     # involved; we currently don't have a neat way to pass the information about nested blocks - the


Test patch:
diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py
--- a/test/python/transpiler/test_preset_passmanagers.py
+++ b/test/python/transpiler/test_preset_passmanagers.py
@@ -1549,8 +1549,6 @@ def test_invalid_methods_raise_on_control_flow(self, optimization_level):
         with qc.for_loop((1,)):
             qc.x(0)
 
-        with self.assertRaisesRegex(TranspilerError, "Got layout_method="):
-            transpile(qc, layout_method="sabre", optimization_level=optimization_level)
         with self.assertRaisesRegex(TranspilerError, "Got routing_method="):
             transpile(qc, routing_method="lookahead", optimization_level=optimization_level)
         with self.assertRaisesRegex(TranspilerError, "Got translation_method="):
diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py
--- a/test/python/transpiler/test_sabre_swap.py
+++ b/test/python/transpiler/test_sabre_swap.py
@@ -14,14 +14,21 @@
 
 import unittest
 
+import itertools
 import ddt
+import numpy.random
 
+from qiskit.circuit import Clbit, ControlFlowOp
 from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate
-from qiskit.converters import circuit_to_dag
-from qiskit.transpiler.passes import SabreSwap, TrivialLayout
-from qiskit.transpiler import CouplingMap, PassManager, Target, TranspilerError
+from qiskit.circuit.random import random_circuit
+from qiskit.compiler.transpiler import transpile
+from qiskit.converters import circuit_to_dag, dag_to_circuit
+from qiskit.providers.fake_provider import FakeMumbai, FakeMumbaiV2
+from qiskit.transpiler.passes import SabreSwap, TrivialLayout, CheckMap
+from qiskit.transpiler import CouplingMap, Layout, PassManager, Target, TranspilerError
 from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
 from qiskit.test import QiskitTestCase
+from qiskit.test._canonical import canonicalize_control_flow
 from qiskit.utils import optionals
 
 
@@ -377,5 +384,879 @@ def test_rejects_too_few_qubits(self):
             pass_(qc)
 
 
+@ddt.ddt
+class TestSabreSwapControlFlow(QiskitTestCase):
+    """Tests for control flow in sabre swap."""
+
+    def test_shared_block(self):
+        """Test multiple control flow ops sharing the same block instance."""
+        inner = QuantumCircuit(2)
+        inner.cx(0, 1)
+
+        qreg = QuantumRegister(4, "q")
+        outer = QuantumCircuit(qreg, ClassicalRegister(1))
+        for pair in itertools.permutations(range(outer.num_qubits), 2):
+            outer.if_test((outer.cregs[0], 1), inner, pair, [])
+
+        coupling = CouplingMap.from_line(4)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(circuit_to_dag(outer))
+
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+    def test_blocks_use_registers(self):
+        """Test that control flow ops using registers still use registers after routing."""
+        num_qubits = 2
+        qreg = QuantumRegister(num_qubits, "q")
+        cr1 = ClassicalRegister(1)
+        cr2 = ClassicalRegister(1)
+        qc = QuantumCircuit(qreg, cr1, cr2)
+
+        with qc.if_test((cr1, False)):
+            qc.cx(0, 1)
+            qc.measure(0, cr2[0])
+            with qc.if_test((cr2, 0)):
+                qc.cx(0, 1)
+
+        coupling = CouplingMap.from_line(num_qubits)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(circuit_to_dag(qc))
+
+        outer_if_op = cdag.op_nodes(ControlFlowOp)[0].op
+        self.assertEqual(outer_if_op.condition[0], cr1)
+
+        inner_if_op = circuit_to_dag(outer_if_op.blocks[0]).op_nodes(ControlFlowOp)[0].op
+        self.assertEqual(inner_if_op.condition[0], cr2)
+
+    def test_pre_if_else_route(self):
+        """test swap with if else controlflow construct"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.measure(2, 2)
+        true_body = QuantumCircuit(qreg, creg[[2]])
+        true_body.x(3)
+        false_body = QuantumCircuit(qreg, creg[[2]])
+        false_body.x(4)
+        qc.if_else((creg[2], 0), true_body, false_body, qreg, creg[[2]])
+        qc.barrier(qreg)
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.measure(1, 2)
+        etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[2]])
+        etrue_body.x(0)
+        efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[2]])
+        efalse_body.x(1)
+        new_order = [0, 2, 1, 3, 4]
+        expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[2]])
+        expected.barrier(qreg)
+        expected.measure(qreg, creg[new_order])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_pre_if_else_route_post_x(self):
+        """test swap with if else controlflow construct; pre-cx and post x"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)])
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.measure(2, 2)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.x(3)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.x(4)
+        qc.if_else((creg[2], 0), true_body, false_body, qreg, creg[[0]])
+        qc.x(1)
+        qc.barrier(qreg)
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.measure(1, 2)
+        new_order = [0, 2, 1, 3, 4]
+        etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[0]])
+        etrue_body.x(0)
+        efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[0]])
+        efalse_body.x(1)
+        expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[0]])
+        expected.x(2)
+        expected.barrier(qreg)
+        expected.measure(qreg, creg[new_order])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_post_if_else_route(self):
+        """test swap with if else controlflow construct; post cx"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)])
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.x(3)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.x(4)
+        qc.barrier(qreg)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.barrier(qreg)
+        qc.cx(0, 2)
+        qc.barrier(qreg)
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg[[3, 4]], creg[[0]])
+        etrue_body.x(0)
+        efalse_body = QuantumCircuit(qreg[[3, 4]], creg[[0]])
+        efalse_body.x(1)
+        expected.barrier(qreg)
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[3, 4]], creg[[0]])
+        expected.barrier(qreg)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.barrier(qreg)
+        expected.measure(qreg, creg[[0, 2, 1, 3, 4]])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_pre_if_else2(self):
+        """test swap with if else controlflow construct; cx in if statement"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)])
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.x(0)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.barrier(qreg)
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg[[0]], creg[[0]])
+        etrue_body.x(0)
+        efalse_body = QuantumCircuit(qreg[[0]], creg[[0]])
+        new_order = [0, 2, 1, 3, 4]
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[0]], creg[[0]])
+        expected.barrier(qreg)
+        expected.measure(qreg, creg[new_order])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_intra_if_else_route(self):
+        """test swap with if else controlflow construct"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)])
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.cx(0, 2)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.cx(0, 4)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg, creg[[0]])
+        etrue_body.swap(1, 2)
+        etrue_body.cx(0, 1)
+        etrue_body.swap(1, 2)
+        efalse_body = QuantumCircuit(qreg, creg[[0]])
+        efalse_body.swap(0, 1)
+        efalse_body.swap(3, 4)
+        efalse_body.swap(2, 3)
+        efalse_body.cx(1, 2)
+        efalse_body.swap(0, 1)
+        efalse_body.swap(2, 3)
+        efalse_body.swap(3, 4)
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]])
+        expected.measure(qreg, creg)
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_pre_intra_if_else(self):
+        """test swap with if else controlflow construct; cx in if statement"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)])
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.cx(0, 2)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.cx(0, 4)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        etrue_body = QuantumCircuit(qreg, creg[[0]])
+        efalse_body = QuantumCircuit(qreg, creg[[0]])
+        expected.h(0)
+        expected.x(1)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.measure(0, 0)
+
+        etrue_body.cx(0, 1)
+
+        efalse_body.swap(0, 1)
+        efalse_body.swap(3, 4)
+        efalse_body.swap(2, 3)
+        efalse_body.cx(1, 2)
+        efalse_body.swap(0, 1)
+        efalse_body.swap(2, 3)
+        efalse_body.swap(3, 4)
+
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]])
+        expected.measure(qreg, creg[[0, 2, 1, 3, 4]])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_pre_intra_post_if_else(self):
+        """test swap with if else controlflow construct; cx before, in, and after if
+        statement"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.cx(0, 2)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.cx(0, 4)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.h(3)
+        qc.cx(3, 0)
+        qc.barrier()
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.swap(0, 1)
+        expected.cx(1, 2)
+        expected.measure(1, 0)
+        etrue_body = QuantumCircuit(qreg[[1, 2, 3, 4]], creg[[0]])
+        etrue_body.cx(0, 1)
+        efalse_body = QuantumCircuit(qreg[[1, 2, 3, 4]], creg[[0]])
+        efalse_body.swap(0, 1)
+        efalse_body.swap(2, 3)
+        efalse_body.cx(1, 2)
+        efalse_body.swap(0, 1)
+        efalse_body.swap(2, 3)
+
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 2, 3, 4]], creg[[0]])
+        expected.h(3)
+        expected.swap(1, 2)
+        expected.cx(3, 2)
+        expected.barrier()
+        expected.measure(qreg, creg[[1, 2, 0, 3, 4]])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_no_layout_change(self):
+        """test controlflow with no layout change needed"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.x(2)
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.x(4)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.barrier(qreg)
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg[[1, 4]], creg[[0]])
+        etrue_body.x(0)
+        efalse_body = QuantumCircuit(qreg[[1, 4]], creg[[0]])
+        efalse_body.x(1)
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 4]], creg[[0]])
+        expected.barrier(qreg)
+        expected.measure(qreg, creg[[0, 2, 1, 3, 4]])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    @ddt.data(1, 2, 3)
+    def test_for_loop(self, nloops):
+        """test stochastic swap with for_loop"""
+        num_qubits = 3
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.x(1)
+        for_body = QuantumCircuit(qreg)
+        for_body.cx(0, 2)
+        loop_parameter = None
+        qc.for_loop(range(nloops), loop_parameter, for_body, qreg, [])
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        efor_body = QuantumCircuit(qreg)
+        efor_body.swap(1, 2)
+        efor_body.cx(0, 1)
+        efor_body.swap(1, 2)
+        loop_parameter = None
+        expected.for_loop(range(nloops), loop_parameter, efor_body, qreg, [])
+        expected.measure(qreg, creg)
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_while_loop(self):
+        """test while loop"""
+        num_qubits = 4
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(len(qreg))
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        while_body = QuantumCircuit(qreg, creg)
+        while_body.reset(qreg[2:])
+        while_body.h(qreg[2:])
+        while_body.cx(0, 3)
+        while_body.measure(qreg[3], creg[3])
+        qc.while_loop((creg, 0), while_body, qc.qubits, qc.clbits)
+        qc.barrier()
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        ewhile_body = QuantumCircuit(qreg, creg)
+        ewhile_body.reset(qreg[2:])
+        ewhile_body.h(qreg[2:])
+        ewhile_body.swap(0, 1)
+        ewhile_body.swap(2, 3)
+        ewhile_body.cx(1, 2)
+        ewhile_body.measure(qreg[2], creg[3])
+        ewhile_body.swap(1, 0)
+        ewhile_body.swap(3, 2)
+        expected.while_loop((creg, 0), ewhile_body, expected.qubits, expected.clbits)
+        expected.barrier()
+        expected.measure(qreg, creg)
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_switch_single_case(self):
+        """Test routing of 'switch' with just a single case."""
+        qreg = QuantumRegister(5, "q")
+        creg = ClassicalRegister(3, "c")
+        qc = QuantumCircuit(qreg, creg)
+
+        case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:])
+        case0.cx(0, 1)
+        case0.cx(1, 2)
+        case0.cx(2, 0)
+        qc.switch(creg, [(0, case0)], qreg[[0, 1, 2]], creg)
+
+        coupling = CouplingMap.from_line(len(qreg))
+        pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1)
+        test = pass_(qc)
+
+        check = CheckMap(coupling)
+        check(test)
+        self.assertTrue(check.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        case0 = QuantumCircuit(qreg[[0, 1, 2]], creg[:])
+        case0.cx(0, 1)
+        case0.cx(1, 2)
+        case0.swap(0, 1)
+        case0.cx(2, 1)
+        case0.swap(0, 1)
+        expected.switch(creg, [(0, case0)], qreg[[0, 1, 2]], creg[:])
+
+        self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected))
+
+    def test_switch_nonexhaustive(self):
+        """Test routing of 'switch' with several but nonexhaustive cases."""
+        qreg = QuantumRegister(5, "q")
+        creg = ClassicalRegister(3, "c")
+
+        qc = QuantumCircuit(qreg, creg)
+        case0 = QuantumCircuit(qreg, creg[:])
+        case0.cx(0, 1)
+        case0.cx(1, 2)
+        case0.cx(2, 0)
+        case1 = QuantumCircuit(qreg, creg[:])
+        case1.cx(1, 2)
+        case1.cx(2, 3)
+        case1.cx(3, 1)
+        case2 = QuantumCircuit(qreg, creg[:])
+        case2.cx(2, 3)
+        case2.cx(3, 4)
+        case2.cx(4, 2)
+        qc.switch(creg, [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg)
+
+        coupling = CouplingMap.from_line(len(qreg))
+        pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1)
+        test = pass_(qc)
+
+        check = CheckMap(coupling)
+        check(test)
+        self.assertTrue(check.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        case0 = QuantumCircuit(qreg, creg[:])
+        case0.cx(0, 1)
+        case0.cx(1, 2)
+        case0.swap(0, 1)
+        case0.cx(2, 1)
+        case0.swap(0, 1)
+        case1 = QuantumCircuit(qreg, creg[:])
+        case1.cx(1, 2)
+        case1.cx(2, 3)
+        case1.swap(1, 2)
+        case1.cx(3, 2)
+        case1.swap(1, 2)
+        case2 = QuantumCircuit(qreg, creg[:])
+        case2.cx(2, 3)
+        case2.cx(3, 4)
+        case2.swap(2, 3)
+        case2.cx(4, 3)
+        case2.swap(2, 3)
+        expected.switch(creg, [(0, case0), ((1, 2), case1), (3, case2)], qreg, creg)
+
+        self.assertEqual(canonicalize_control_flow(test), canonicalize_control_flow(expected))
+
+    def test_nested_inner_cnot(self):
+        """test swap in nested if else controlflow construct; swap in inner"""
+        num_qubits = 3
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.x(0)
+
+        for_body = QuantumCircuit(qreg)
+        for_body.delay(10, 0)
+        for_body.barrier(qreg)
+        for_body.cx(0, 2)
+        loop_parameter = None
+        true_body.for_loop(range(3), loop_parameter, for_body, qreg, [])
+
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.y(0)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg, creg[[0]])
+        etrue_body.x(0)
+
+        efor_body = QuantumCircuit(qreg)
+        efor_body.delay(10, 0)
+        efor_body.barrier(qreg)
+        efor_body.swap(1, 2)
+        efor_body.cx(0, 1)
+        efor_body.swap(1, 2)
+        etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg, [])
+
+        efalse_body = QuantumCircuit(qreg, creg[[0]])
+        efalse_body.y(0)
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]])
+        expected.measure(qreg, creg)
+
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_nested_outer_cnot(self):
+        """test swap with nested if else controlflow construct; swap in outer"""
+        num_qubits = 5
+        qreg = QuantumRegister(num_qubits, "q")
+        creg = ClassicalRegister(num_qubits)
+        coupling = CouplingMap.from_line(num_qubits)
+        qc = QuantumCircuit(qreg, creg)
+        qc.h(0)
+        qc.x(1)
+        qc.measure(0, 0)
+        true_body = QuantumCircuit(qreg, creg[[0]])
+        true_body.cx(0, 2)
+        true_body.x(0)
+
+        for_body = QuantumCircuit(qreg)
+        for_body.delay(10, 0)
+        for_body.barrier(qreg)
+        for_body.cx(1, 3)
+        loop_parameter = None
+        true_body.for_loop(range(3), loop_parameter, for_body, qreg, [])
+
+        false_body = QuantumCircuit(qreg, creg[[0]])
+        false_body.y(0)
+        qc.if_else((creg[0], 0), true_body, false_body, qreg, creg[[0]])
+        qc.measure(qreg, creg)
+
+        dag = circuit_to_dag(qc)
+        cdag = SabreSwap(coupling, "lookahead", seed=82, trials=1).run(dag)
+        check_map_pass = CheckMap(coupling)
+        check_map_pass.run(cdag)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qreg, creg)
+        expected.h(0)
+        expected.x(1)
+        expected.measure(0, 0)
+        etrue_body = QuantumCircuit(qreg, creg[[0]])
+        etrue_body.swap(1, 2)
+        etrue_body.cx(0, 1)
+        etrue_body.x(0)
+
+        efor_body = QuantumCircuit(qreg)
+        efor_body.delay(10, 0)
+        efor_body.barrier(qreg)
+        efor_body.cx(2, 3)
+
+        etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg[[0, 1, 2, 3, 4]], [])
+        etrue_body.swap(1, 2)
+
+        efalse_body = QuantumCircuit(qreg, creg[[0]])
+        efalse_body.y(0)
+        expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg[[0]])
+        expected.measure(qreg, creg[[0, 1, 2, 3, 4]])
+        self.assertEqual(dag_to_circuit(cdag), expected)
+
+    def test_disjoint_looping(self):
+        """Test looping controlflow on different qubit register"""
+        num_qubits = 4
+        cm = CouplingMap.from_line(num_qubits)
+        qr = QuantumRegister(num_qubits, "q")
+        qc = QuantumCircuit(qr)
+        loop_body = QuantumCircuit(2)
+        loop_body.cx(0, 1)
+        qc.for_loop((0,), None, loop_body, [0, 2], [])
+        cqc = SabreSwap(cm, "lookahead", seed=82, trials=1)(qc)
+
+        expected = QuantumCircuit(qr)
+        efor_body = QuantumCircuit(qr[[0, 1, 2]])
+        efor_body.swap(1, 2)
+        efor_body.cx(0, 1)
+        efor_body.swap(1, 2)
+        expected.for_loop((0,), None, efor_body, [0, 1, 2], [])
+        self.assertEqual(cqc, expected)
+
+    def test_disjoint_multiblock(self):
+        """Test looping controlflow on different qubit register"""
+        num_qubits = 4
+        cm = CouplingMap.from_line(num_qubits)
+        qr = QuantumRegister(num_qubits, "q")
+        cr = ClassicalRegister(1)
+        qc = QuantumCircuit(qr, cr)
+        true_body = QuantumCircuit(qr[0:3] + cr[:])
+        true_body.cx(0, 1)
+        false_body = QuantumCircuit(qr[0:3] + cr[:])
+        false_body.cx(0, 2)
+        qc.if_else((cr[0], 1), true_body, false_body, [0, 1, 2], [0])
+        cqc = SabreSwap(cm, "lookahead", seed=82, trials=1)(qc)
+
+        expected = QuantumCircuit(qr, cr)
+        etrue_body = QuantumCircuit(qr[[0, 1, 2]], cr[[0]])
+        etrue_body.cx(0, 1)
+        efalse_body = QuantumCircuit(qr[[0, 1, 2]], cr[[0]])
+        efalse_body.swap(1, 2)
+        efalse_body.cx(0, 1)
+        efalse_body.swap(1, 2)
+        expected.if_else((cr[0], 1), etrue_body, efalse_body, [0, 1, 2], cr[[0]])
+        self.assertEqual(cqc, expected)
+
+    def test_multiple_ops_per_layer(self):
+        """Test circuits with multiple operations per layer"""
+        num_qubits = 6
+        coupling = CouplingMap.from_line(num_qubits)
+        check_map_pass = CheckMap(coupling)
+        qr = QuantumRegister(num_qubits, "q")
+        qc = QuantumCircuit(qr)
+        # This cx and the for_loop are in the same layer.
+        qc.cx(0, 2)
+        with qc.for_loop((0,)):
+            qc.cx(3, 5)
+        cqc = SabreSwap(coupling, "lookahead", seed=82, trials=1)(qc)
+        check_map_pass(cqc)
+        self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
+
+        expected = QuantumCircuit(qr)
+        expected.swap(1, 2)
+        expected.cx(0, 1)
+        efor_body = QuantumCircuit(qr[[3, 4, 5]])
+        efor_body.swap(1, 2)
+        efor_body.cx(0, 1)
+        efor_body.swap(2, 1)
+        expected.for_loop((0,), None, efor_body, [3, 4, 5], [])
+        self.assertEqual(cqc, expected)
+
+    def test_if_no_else_restores_layout(self):
+        """Test that an if block with no else branch restores the initial layout."""
+        qc = QuantumCircuit(8, 1)
+        with qc.if_test((qc.clbits[0], False)):
+            # Just some arbitrary gates with no perfect layout.
+            qc.cx(3, 5)
+            qc.cx(4, 6)
+            qc.cx(1, 4)
+            qc.cx(7, 4)
+            qc.cx(0, 5)
+            qc.cx(7, 3)
+            qc.cx(1, 3)
+            qc.cx(5, 2)
+            qc.cx(6, 7)
+            qc.cx(3, 2)
+            qc.cx(6, 2)
+            qc.cx(2, 0)
+            qc.cx(7, 6)
+        coupling = CouplingMap.from_line(8)
+        pass_ = SabreSwap(coupling, "lookahead", seed=82, trials=1)
+        transpiled = pass_(qc)
+
+        # Check the pass claims to have done things right.
+        initial_layout = Layout.generate_trivial_layout(*qc.qubits)
+        self.assertEqual(initial_layout, pass_.property_set["final_layout"])
+
+        # Check that pass really did do it right.
+        inner_block = transpiled.data[0].operation.blocks[0]
+        running_layout = initial_layout.copy()
+        for instruction in inner_block:
+            if instruction.operation.name == "swap":
+                running_layout.swap(*instruction.qubits)
+        self.assertEqual(initial_layout, running_layout)
+
+
+@ddt.ddt
+class TestSabreSwapRandomCircuitValidOutput(QiskitTestCase):
+    """Assert the output of a transpilation with stochastic swap is a physical circuit."""
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.backend = FakeMumbai()
+        cls.coupling_edge_set = {tuple(x) for x in cls.backend.configuration().coupling_map}
+        cls.basis_gates = set(cls.backend.configuration().basis_gates)
+        cls.basis_gates.update(["for_loop", "while_loop", "if_else"])
+
+    def assert_valid_circuit(self, transpiled):
+        """Assert circuit complies with constraints of backend."""
+        self.assertIsInstance(transpiled, QuantumCircuit)
+        self.assertIsNotNone(getattr(transpiled, "_layout", None))
+
+        def _visit_block(circuit, qubit_mapping=None):
+            for instruction in circuit:
+                if instruction.operation.name in {"barrier", "measure"}:
+                    continue
+                self.assertIn(instruction.operation.name, self.basis_gates)
+                qargs = tuple(qubit_mapping[x] for x in instruction.qubits)
+                if not isinstance(instruction.operation, ControlFlowOp):
+                    if len(qargs) > 2 or len(qargs) < 0:
+                        raise Exception("Invalid number of qargs for instruction")
+                    if len(qargs) == 2:
+                        self.assertIn(qargs, self.coupling_edge_set)
+                    else:
+                        self.assertLessEqual(qargs[0], 26)
+                else:
+                    for block in instruction.operation.blocks:
+                        self.assertEqual(block.num_qubits, len(instruction.qubits))
+                        self.assertEqual(block.num_clbits, len(instruction.clbits))
+                        new_mapping = {
+                            inner: qubit_mapping[outer]
+                            for outer, inner in zip(instruction.qubits, block.qubits)
+                        }
+                        _visit_block(block, new_mapping)
+
+        # Assert routing ran.
+        _visit_block(
+            transpiled,
+            qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)},
+        )
+
+    @ddt.data(*range(1, 27))
+    def test_random_circuit_no_control_flow(self, size):
+        """Test that transpiled random circuits without control flow are physical circuits."""
+        circuit = random_circuit(size, 3, measure=True, seed=12342)
+        tqc = transpile(
+            circuit,
+            self.backend,
+            routing_method="sabre",
+            layout_method="sabre",
+            seed_transpiler=12342,
+        )
+        self.assert_valid_circuit(tqc)
+
+    @ddt.data(*range(1, 27))
+    def test_random_circuit_no_control_flow_target(self, size):
+        """Test that transpiled random circuits without control flow are physical circuits."""
+        circuit = random_circuit(size, 3, measure=True, seed=12342)
+        tqc = transpile(
+            circuit,
+            routing_method="sabre",
+            layout_method="sabre",
+            seed_transpiler=12342,
+            target=FakeMumbaiV2().target,
+        )
+        self.assert_valid_circuit(tqc)
+
+    @ddt.data(*range(4, 27))
+    def test_random_circuit_for_loop(self, size):
+        """Test that transpiled random circuits with nested for loops are physical circuits."""
+        circuit = random_circuit(size, 3, measure=False, seed=12342)
+        for_block = random_circuit(3, 2, measure=False, seed=12342)
+        inner_for_block = random_circuit(2, 1, measure=False, seed=12342)
+        with circuit.for_loop((1,)):
+            with circuit.for_loop((1,)):
+                circuit.append(inner_for_block, [0, 3])
+            circuit.append(for_block, [1, 0, 2])
+        circuit.measure_all()
+
+        tqc = transpile(
+            circuit,
+            self.backend,
+            basis_gates=list(self.basis_gates),
+            routing_method="sabre",
+            layout_method="sabre",
+            seed_transpiler=12342,
+        )
+        self.assert_valid_circuit(tqc)
+
+    @ddt.data(*range(6, 27))
+    def test_random_circuit_if_else(self, size):
+        """Test that transpiled random circuits with if else blocks are physical circuits."""
+        circuit = random_circuit(size, 3, measure=True, seed=12342)
+        if_block = random_circuit(3, 2, measure=True, seed=12342)
+        else_block = random_circuit(2, 1, measure=True, seed=12342)
+
+        rng = numpy.random.default_rng(seed=12342)
+        inner_clbit_count = max((if_block.num_clbits, else_block.num_clbits))
+        if inner_clbit_count > circuit.num_clbits:
+            circuit.add_bits([Clbit() for _ in [None] * (inner_clbit_count - circuit.num_clbits)])
+        clbit_indices = list(range(circuit.num_clbits))
+        rng.shuffle(clbit_indices)
+
+        with circuit.if_test((circuit.clbits[0], True)) as else_:
+            circuit.append(if_block, [0, 2, 1], clbit_indices[: if_block.num_clbits])
+        with else_:
+            circuit.append(else_block, [2, 5], clbit_indices[: else_block.num_clbits])
+
+        tqc = transpile(
+            circuit,
+            self.backend,
+            basis_gates=list(self.basis_gates),
+            routing_method="sabre",
+            layout_method="sabre",
+            seed_transpiler=12342,
+        )
+        self.assert_valid_circuit(tqc)
+
+
 if __name__ == "__main__":
     unittest.main()