Repository: Qiskit/qiskit — pull request #8041 (python)
Title: Fix SabreSwap with classically conditioned gates

### Summary\r\n\r\nThe calculation of the expected number of predecessors for a gate to\r\nbe considered "resolved" in `SabreSwap` was not accounting for wires\r\nstemming from classical conditions.  The tracking of the _actual_ number\r\nof predecessor requirements satisfied did correctly account for this, so\r\nin certain circumstances the actual count could jump from "too low" to\r\n"too high" without passing through "just right", and the gate would\r\nnever get added to the circuit.\r\n\r\n<!--\r\n⚠️ If you do not respect this template, your pull request will be closed.\r\n⚠️ Your pull request title should be short detailed and understandable for all.\r\n⚠️ Also, please add a release note file using reno if the change needs to be\r\n  documented in the release notes.\r\n⚠️ If your pull request fixes an open issue, please link to the issue.\r\n\r\n- [ ] I have added the tests to cover my changes.\r\n- [ ] I have updated the documentation accordingly.\r\n- [ ] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n\r\n\r\n### Details and comments\r\n\r\nFix #8040.

Resolved issues:
title: `SabreSwap` can throw away classically conditioned gates and all successors
body: ### Environment\r\n\r\n- **Qiskit Terra version**: main @ 7726d4704\r\n- **Python version**: any\r\n- **Operating system**: any\r\n\r\n\r\n### What is happening?\r\n\r\nThe `SabreSwap` transpiler pass can sometimes remove classically conditioned gates from the circuit, and all their successors, whether or not the successors have classical conditions.\r\n\r\nThis is (I believe) the cause of the current failures in the randomised test suite (from https://github.com/Qiskit/qiskit-terra/issues/2645#issuecomment-1111747082 to https://github.com/Qiskit/qiskit-terra/issues/2645#issuecomment-1121930469).\r\n\r\n### How can we reproduce the issue?\r\n\r\n```python\r\nIn [1]: from qiskit import QuantumCircuit\r\n   ...: from qiskit.transpiler import PassManager, CouplingMap\r\n   ...: from qiskit.transpiler.passes import SabreSwap, TrivialLayout\r\n   ...:\r\n   ...: qc = QuantumCircuit(2, 1)\r\n   ...: qc.z(0)\r\n   ...: qc.z(0).c_if(qc.cregs[0], 0)\r\n   ...: print(qc.draw())\r\n   ...: print()\r\n   ...: cm = CouplingMap([(0, 1), (1, 0)])\r\n   ...: print(PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc).draw())\r\n     ┌───┐ ┌───┐\r\nq_0: ┤ Z ├─┤ Z ├─\r\n     └───┘ └─╥─┘\r\nq_1: ────────╫───\r\n          ┌──╨──┐\r\nc: 1/═════╡ 0x0 ╞\r\n          └─────┘\r\n\r\n         ┌───┐\r\nq_0 -> 0 ┤ Z ├\r\n         └───┘\r\nq_1 -> 1 ─────\r\n              \r\n    c: 1/═════           \r\n```\r\n\r\n### What should happen?\r\n\r\nThe transpiled circuit should not have lost the second classically controlled gate.\r\n\r\n### Any suggestions?\r\n\r\nConditions currently aren't being accounted for in the "expected number of predecessors" calculation that determines whether a gate is eligible to be added to the circuit.  The current `_is_resolved` check, since #7952, is:\r\n```python\r\nself.applied_predecessors[node] == len(node.qargs) + len(node.cargs)\r\n```\r\nbut conditions are not part of the `cargs`.  Since the condition is _equality_ to the known value, if the relevant node is not in the original front layer, its `applied_predecessors` (which correctly account for conditions) can be increased beyond the count of its qargs and cargs, so the condition never triggers.\r\n\r\n(#7952 didn't cause this issue, it just didn't completely address the issue of all classical arguments.)\r\n\r\nThe fix is to account for all possible predecessors in `SabreSwap._is_resolved`.

Hints:

Fix patch:
diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py\nindex c6555b069f10..1a1530508d4f 100644\n--- a/qiskit/transpiler/passes/routing/sabre_swap.py\n+++ b/qiskit/transpiler/passes/routing/sabre_swap.py\n@@ -139,7 +139,7 @@ def __init__(\n         self.heuristic = heuristic\n         self.seed = seed\n         self.fake_run = fake_run\n-        self.applied_predecessors = None\n+        self.required_predecessors = None\n         self.qubits_decay = None\n         self._bit_indices = None\n         self.dist_matrix = None\n@@ -188,12 +188,9 @@ def run(self, dag):\n         self.qubits_decay = dict.fromkeys(dag.qubits, 1)\n \n         # Start algorithm from the front layer and iterate until all gates done.\n+        self.required_predecessors = self._build_required_predecessors(dag)\n         num_search_steps = 0\n         front_layer = dag.front_layer()\n-        self.applied_predecessors = defaultdict(int)\n-        for _, input_node in dag.input_map.items():\n-            for successor in self._successors(input_node, dag):\n-                self.applied_predecessors[successor] += 1\n \n         while front_layer:\n             execute_gate_list = []\n@@ -228,7 +225,7 @@ def run(self, dag):\n                 for node in execute_gate_list:\n                     self._apply_gate(mapped_dag, node, current_layout, canonical_register)\n                     for successor in self._successors(node, dag):\n-                        self.applied_predecessors[successor] += 1\n+                        self.required_predecessors[successor] -= 1\n                         if self._is_resolved(successor):\n                             front_layer.append(successor)\n \n@@ -314,6 +311,15 @@ def _reset_qubits_decay(self):\n         """\n         self.qubits_decay = {k: 1 for k in self.qubits_decay.keys()}\n \n+    def _build_required_predecessors(self, dag):\n+        out = defaultdict(int)\n+        # We don't need to count in- or out-wires: outs can never be predecessors, and all input\n+        # wires are automatically satisfied at the start.\n+        for node in dag.op_nodes():\n+            for successor in self._successors(node, dag):\n+                out[successor] += 1\n+        return out\n+\n     def _successors(self, node, dag):\n         """Return an iterable of the successors along each wire from the given node.\n \n@@ -326,22 +332,22 @@ def _successors(self, node, dag):\n \n     def _is_resolved(self, node):\n         """Return True if all of a node's predecessors in dag are applied."""\n-        return self.applied_predecessors[node] == len(node.qargs) + len(node.cargs)\n+        return self.required_predecessors[node] == 0\n \n     def _obtain_extended_set(self, dag, front_layer):\n         """Populate extended_set by looking ahead a fixed number of gates.\n         For each existing element add a successor until reaching limit.\n         """\n         extended_set = []\n-        incremented = []\n+        decremented = []\n         tmp_front_layer = front_layer\n         done = False\n         while tmp_front_layer and not done:\n             new_tmp_front_layer = []\n             for node in tmp_front_layer:\n                 for successor in self._successors(node, dag):\n-                    incremented.append(successor)\n-                    self.applied_predecessors[successor] += 1\n+                    decremented.append(successor)\n+                    self.required_predecessors[successor] -= 1\n                     if self._is_resolved(successor):\n                         new_tmp_front_layer.append(successor)\n                         if len(successor.qargs) == 2:\n@@ -350,8 +356,8 @@ def _obtain_extended_set(self, dag, front_layer):\n                     done = True\n                     break\n             tmp_front_layer = new_tmp_front_layer\n-        for node in incremented:\n-            self.applied_predecessors[node] -= 1\n+        for node in decremented:\n+            self.required_predecessors[node] += 1\n         return extended_set\n \n     def _obtain_swaps(self, front_layer, current_layout):\ndiff --git a/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml b/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml\nnew file mode 100644\nindex 000000000000..376014ce45b8\n--- /dev/null\n+++ b/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml\n@@ -0,0 +1,8 @@\n+---\n+fixes:\n+  - |\n+    The :class:`.SabreSwap` transpiler pass, used in :func:`.transpile` when\n+    ``routing_method="sabre"`` is set, will no longer sporadically drop\n+    classically conditioned gates and their successors from circuits during the\n+    routing phase of transpilation.  See\n+    `#8040 <https://github.com/Qiskit/qiskit-terra/issues/8040>`__.\n

Test patch:
diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py\nindex f7a9e96cfd62..89e43b91491d 100644\n--- a/test/python/transpiler/test_sabre_swap.py\n+++ b/test/python/transpiler/test_sabre_swap.py\n@@ -17,9 +17,9 @@\n import ddt\n \n from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate\n-from qiskit.transpiler.passes import SabreSwap\n+from qiskit.transpiler.passes import SabreSwap, TrivialLayout\n from qiskit.transpiler import CouplingMap, PassManager\n-from qiskit import QuantumRegister, QuantumCircuit\n+from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit\n from qiskit.test import QiskitTestCase\n from qiskit.utils import optionals\n \n@@ -237,6 +237,31 @@ def leak_number_of_swaps(cls, *args, **kwargs):\n         out_results = sim.run(routed, shots=4096).result().get_counts()\n         self.assertEqual(set(in_results), set(out_results))\n \n+    def test_classical_condition(self):\n+        """Test that :class:`.SabreSwap` correctly accounts for classical conditions in its\n+        reckoning on whether a node is resolved or not.  If it is not handled correctly, the second\n+        gate might not appear in the output.\n+\n+        Regression test of gh-8040."""\n+        with self.subTest("1 bit in register"):\n+            qc = QuantumCircuit(2, 1)\n+            qc.z(0)\n+            qc.z(0).c_if(qc.cregs[0], 0)\n+            cm = CouplingMap([(0, 1), (1, 0)])\n+            expected = PassManager([TrivialLayout(cm)]).run(qc)\n+            actual = PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc)\n+            self.assertEqual(expected, actual)\n+        with self.subTest("multiple registers"):\n+            cregs = [ClassicalRegister(3), ClassicalRegister(4)]\n+            qc = QuantumCircuit(QuantumRegister(2, name="q"), *cregs)\n+            qc.z(0)\n+            qc.z(0).c_if(cregs[0], 0)\n+            qc.z(0).c_if(cregs[1], 0)\n+            cm = CouplingMap([(0, 1), (1, 0)])\n+            expected = PassManager([TrivialLayout(cm)]).run(qc)\n+            actual = PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc)\n+            self.assertEqual(expected, actual)\n+\n \n if __name__ == "__main__":\n     unittest.main()\n