Repository: Qiskit/qiskit

Issue:
SabreSwap can reorder or throw away operations near conditions
### Environment

- **Qiskit Terra version**: main @ a4cf148
- **Python version**: 3.10
- **Operating system**: macOS


### What is happening?

Randomised testing turned up a case in `SabreSwap` (since the merge of #8388) where it appeared to throw away a measurement gate: see https://github.com/Qiskit/qiskit-terra/issues/2645#issuecomment-1236260208.  Tweaking the qubits and conditionals there results in different parts of the circuit being dropped.

It seems to be related to conditionals, but I'm not 100% sure.

### How can we reproduce the issue?

For example,

```python
import qiskit
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import SabreSwap
qc = qiskit.QuantumCircuit(3, 1)
qc.cx(0, 2).c_if(0, 0)
qc.measure(1, 0)
qc.h(2).c_if(0, 0)
out = SabreSwap(CouplingMap.from_line(3)).run(circuit_to_dag(qc))
dag_to_circuit(out).draw()
```
gives
```text
q_0: ────────■────────────────
           ┌─┴─┐      ┌───┐
q_1: ─X────┤ X ├──────┤ H ├───
      │    └─╥─┘      └─╥─┘
q_2: ─X──────╫──────────╫─────
        ┌────╨────┐┌────╨────┐
c: 1/═══╡ c_0=0x0 ╞╡ c_0=0x0 ╞
        └─────────┘└─────────┘
```

while changing the `cx(0, 2)` to `cx(0, 1)` and the `h(2)` to `h(0)` causes the circuit to be invalidly re-ordered:
```text
                   ┌───┐
q_0: ─────■────────┤ H ├──────
        ┌─┴─┐      └─╥─┘   ┌─┐
q_1: ───┤ X ├────────╫─────┤M├
        └─╥─┘        ║     └╥┘
q_2: ─────╫──────────╫──────╫─
     ┌────╨────┐┌────╨────┐ ║
c: 1/╡ c_0=0x0 ╞╡ c_0=0x0 ╞═╩═
     └─────────┘└─────────┘ 0
```
(_mathematically_ that re-ordering is technically valid, since we assume qubits start in $\lvert0\rangle$ so the clbit can ideally never be 1, but Sabre doesn't know that at this point, so it's doing something wrong.)

### What should happen?

Classical wire ordering should be respected as well, even if the wires are only in conditions.

### Any suggestions?

Looks like the Rust-ified version of Sabre is back to its old tricks of not correctly respecting wires in conditions, somehow or another.  We fixed similar issues in #7952 and #8041, so something similar might be up again (though not exactly the same, since those added regression tests).  Dropping inputs has previously meant that the wire predecessor / successor counts aren't being handled correctly.


Discussion:

Resolving patch:
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
@@ -210,11 +210,16 @@ def run(self, dag):
 
         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_condition(node.op.condition):
+                    cargs.add(self._clbit_indices[clbit])
+
             dag_list.append(
                 (
                     node._node_id,
                     [self._qubit_indices[x] for x in node.qargs],
-                    [self._clbit_indices[x] for x in node.cargs],
+                    cargs,
                 )
             )
         front_layer = np.asarray([x._node_id for x in dag.front_layer()], dtype=np.uintp)


Test patch:
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
@@ -262,6 +262,51 @@ def test_classical_condition(self):
             actual = PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc)
             self.assertEqual(expected, actual)
 
+    def test_classical_condition_cargs(self):
+        """Test that classical conditions are preserved even if missing from cargs DAGNode field.
+
+        Created from reproduction in https://github.com/Qiskit/qiskit-terra/issues/8675
+        """
+        with self.subTest("missing measurement"):
+            qc = QuantumCircuit(3, 1)
+            qc.cx(0, 2).c_if(0, 0)
+            qc.measure(1, 0)
+            qc.h(2).c_if(0, 0)
+            expected = QuantumCircuit(3, 1)
+            expected.swap(1, 2)
+            expected.cx(0, 1).c_if(0, 0)
+            expected.measure(2, 0)
+            expected.h(1).c_if(0, 0)
+            result = SabreSwap(CouplingMap.from_line(3), seed=12345)(qc)
+            self.assertEqual(result, expected)
+        with self.subTest("reordered measurement"):
+            qc = QuantumCircuit(3, 1)
+            qc.cx(0, 1).c_if(0, 0)
+            qc.measure(1, 0)
+            qc.h(0).c_if(0, 0)
+            expected = QuantumCircuit(3, 1)
+            expected.cx(0, 1).c_if(0, 0)
+            expected.measure(1, 0)
+            expected.h(0).c_if(0, 0)
+            result = SabreSwap(CouplingMap.from_line(3), seed=12345)(qc)
+            self.assertEqual(result, expected)
+
+    def test_conditional_measurement(self):
+        """Test that instructions with cargs and conditions are handled correctly."""
+        qc = QuantumCircuit(3, 2)
+        qc.cx(0, 2).c_if(0, 0)
+        qc.measure(2, 0).c_if(1, 0)
+        qc.h(2).c_if(0, 0)
+        qc.measure(1, 1)
+        expected = QuantumCircuit(3, 2)
+        expected.swap(1, 2)
+        expected.cx(0, 1).c_if(0, 0)
+        expected.measure(1, 0).c_if(1, 0)
+        expected.h(1).c_if(0, 0)
+        expected.measure(2, 1)
+        result = SabreSwap(CouplingMap.from_line(3), seed=12345)(qc)
+        self.assertEqual(result, expected)
+
 
 if __name__ == "__main__":
     unittest.main()