Repository: Qiskit/qiskit

Issue:
`CheckMap` can raise exceptions with nested classical conditions
### Environment

- **Qiskit Terra version**: 0.24.1
- **Python version**: 3.10
- **Operating system**: macOS

From a thread on public Slack.

### What is happening?

It's possible for `CheckMap` to raise a spurious exception during a run on a valid circuit, with it failing in a call `DAGCircuit.compose`.  This can happen when a conditional block contains another conditional, and the clbits of the inner block are not in the same order in the `QuantumCircuit` object in the inner block as they are in the outer, even if the `qargs` binding between the two maps them normally.

### How can we reproduce the issue?

```python
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import CheckMap

qr = QuantumRegister(2, "q")
cr1 = ClassicalRegister(2, "c1")
cr2 = ClassicalRegister(2, "c2")

# Note that the bits here are not in the same order as in the outer circuit
# object, but they are the same as the binding order in the `if_test`, so
# everything maps `{x: x}` and it should all be fine.
inner_order = [cr2[0], cr1[0], cr2[1], cr1[1]]
inner = QuantumCircuit(qr, inner_order, cr1, cr2)
inner.x(0).c_if(cr2, 3)

outer = QuantumCircuit(qr, cr1, cr2)
outer.if_test((cr1, 3), inner, outer.qubits, inner_order)

CheckMap(CouplingMap.from_line(2))(outer)
```
```text
---------------------------------------------------------------------------
DAGCircuitError                           Traceback (most recent call last)
<ipython-input-1-226f23e404d8> in <module>
     17 outer.if_test((cr1, 3), inner, outer.qubits, inner_order)
     18
---> 19 CheckMap(CouplingMap.from_line(2))(outer)

~/code/qiskit/terra/qiskit/transpiler/basepasses.py in __call__(self, circuit, property_set)
     76             self.property_set = property_set_
     77
---> 78         result = self.run(circuit_to_dag(circuit))
     79
     80         result_circuit = circuit

~/code/qiskit/terra/qiskit/transpiler/passes/utils/check_map.py in run(self, dag)
     93                     dag_block = circuit_to_dag(block)
     94                     mapped_dag = dag.copy_empty_like()
---> 95                     mapped_dag.compose(dag_block, qubits=order)
     96                     self.run(mapped_dag)
     97                     if not self.property_set[self.property_set_field]:

~/code/qiskit/terra/qiskit/dagcircuit/dagcircuit.py in compose(self, other, qubits, clbits, front, inplace)
    820                 pass
    821             elif isinstance(nd, DAGOpNode):
--> 822                 condition = dag._map_condition(
    823                     edge_map, getattr(nd.op, "condition", None), dag.cregs.values()
    824                 )

~/code/qiskit/terra/qiskit/dagcircuit/dagcircuit.py in _map_condition(wire_map, condition, target_cregs)
    682                     # Raise if wire_map maps condition creg on to more than one
    683                     # creg in target DAG.
--> 684                     raise DAGCircuitError(
    685                         "wire_map maps conditional register onto more than one creg."
    686                     )

DAGCircuitError: 'wire_map maps conditional register onto more than one creg.'
```

### What should happen?

`CheckMap` shouldn't raise exceptions.

### Any suggestions?

_No response_


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py
--- a/qiskit/transpiler/passes/utils/check_map.py
+++ b/qiskit/transpiler/passes/utils/check_map.py
@@ -15,6 +15,7 @@
 from qiskit.transpiler.basepasses import AnalysisPass
 from qiskit.transpiler.target import Target
 from qiskit.circuit.controlflow import ControlFlowOp
+from qiskit.converters import circuit_to_dag
 
 
 class CheckMap(AnalysisPass):
@@ -64,35 +65,30 @@ def run(self, dag):
         Args:
             dag (DAGCircuit): DAG to map.
         """
-        from qiskit.converters import circuit_to_dag
-
-        self.property_set[self.property_set_field] = True
-
         if not self.qargs:
+            self.property_set[self.property_set_field] = True
             return
+        wire_map = {bit: index for index, bit in enumerate(dag.qubits)}
+        self.property_set[self.property_set_field] = self._recurse(dag, wire_map)
 
-        qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
+    def _recurse(self, dag, wire_map) -> bool:
         for node in dag.op_nodes(include_directives=False):
-            is_controlflow_op = isinstance(node.op, ControlFlowOp)
-            if len(node.qargs) == 2 and not is_controlflow_op:
-                if dag.has_calibration_for(node):
-                    continue
-                physical_q0 = qubit_indices[node.qargs[0]]
-                physical_q1 = qubit_indices[node.qargs[1]]
-                if (physical_q0, physical_q1) not in self.qargs:
-                    self.property_set["check_map_msg"] = "{}({}, {}) failed".format(
-                        node.name,
-                        physical_q0,
-                        physical_q1,
-                    )
-                    self.property_set[self.property_set_field] = False
-                    return
-            elif is_controlflow_op:
-                order = [qubit_indices[bit] for bit in node.qargs]
+            if isinstance(node.op, ControlFlowOp):
                 for block in node.op.blocks:
-                    dag_block = circuit_to_dag(block)
-                    mapped_dag = dag.copy_empty_like()
-                    mapped_dag.compose(dag_block, qubits=order)
-                    self.run(mapped_dag)
-                    if not self.property_set[self.property_set_field]:
-                        return
+                    inner_wire_map = {
+                        inner: wire_map[outer] for inner, outer in zip(block.qubits, node.qargs)
+                    }
+                    if not self._recurse(circuit_to_dag(block), inner_wire_map):
+                        return False
+            elif (
+                len(node.qargs) == 2
+                and not dag.has_calibration_for(node)
+                and (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) not in self.qargs
+            ):
+                self.property_set["check_map_msg"] = "{}({}, {}) failed".format(
+                    node.name,
+                    wire_map[node.qargs[0]],
+                    wire_map[node.qargs[1]],
+                )
+                return False
+        return True


Test patch:
diff --git a/test/python/transpiler/test_check_map.py b/test/python/transpiler/test_check_map.py
--- a/test/python/transpiler/test_check_map.py
+++ b/test/python/transpiler/test_check_map.py
@@ -255,6 +255,28 @@ def test_nested_controlflow_false(self):
         pass_.run(dag)
         self.assertFalse(pass_.property_set["is_swap_mapped"])
 
+    def test_nested_conditional_unusual_bit_order(self):
+        """Test that `CheckMap` succeeds when inner conditional blocks have clbits that are involved
+        in their own (nested conditionals), and the binding order is not the same as the
+        bit-definition order.  See gh-10394."""
+        qr = QuantumRegister(2, "q")
+        cr1 = ClassicalRegister(2, "c1")
+        cr2 = ClassicalRegister(2, "c2")
+
+        # Note that the bits here are not in the same order as in the outer circuit object, but they
+        # are the same as the binding order in the `if_test`, so everything maps `{x: x}` and it
+        # should all be fine.  This kind of thing is a staple of the control-flow builders.
+        inner_order = [cr2[0], cr1[0], cr2[1], cr1[1]]
+        inner = QuantumCircuit(qr, inner_order, cr1, cr2)
+        inner.cx(0, 1).c_if(cr2, 3)
+
+        outer = QuantumCircuit(qr, cr1, cr2)
+        outer.if_test((cr1, 3), inner, outer.qubits, inner_order)
+
+        pass_ = CheckMap(CouplingMap.from_line(2))
+        pass_(outer)
+        self.assertTrue(pass_.property_set["is_swap_mapped"])
+
 
 if __name__ == "__main__":
     unittest.main()