Repository: Qiskit/qiskit

Issue:
`BlockCollapser` doesnt work with Clbits
### Environment

- **Qiskit Terra version**: 0.23.2
- **Python version**: 3.9
- **Operating system**: MacOS


### What is happening?

I am trying to use `BlockCollector` and `BlockCollapser` with filter functions that include circuit ops with classical bits, which currently doesn't work.

### How can we reproduce the issue?

Here is a simple example where first I tried using the BlockCollapser:

```python
from qiskit import QuantumCircuit
from qiskit.circuit import Measure
from qiskit.converters import circuit_to_dag, dag_to_circuit, circuit_to_instruction
from qiskit.dagcircuit import BlockCollector, BlockCollapser

qc = QuantumCircuit(3)
qc.h(range(3))
qc.measure_all()

dag = circuit_to_dag(qc)

# Collect all measure instructions
blocks_meas = BlockCollector(dag).collect_all_matching_blocks(
    lambda node: isinstance(node.op, Measure),
    split_blocks=False,
    min_block_size=1
)

def _collapse_fn(block):
    op = circuit_to_instruction(block)
    op.name = "COLLAPSED"
    return op

# Collapse measure blocks to a single instruction
BlockCollapser(dag).collapse_to_operation(blocks_meas, _collapse_fn)
```
This raises `raises CircuitError: "Bit 'Clbit(ClassicalRegister(3, 'meas'), 2)' is not in the circuit.`

### What should happen?

I would like to be able to use `BlockCollapser` (and implicitly `DAGCircuit.replace_block_with_op`) with blocks that also contain Clbits.

### Any suggestions?

I took a stab at trying to modify the collapse functionto try different handling to include clbits in `wire_pos_map` and when `DAGCircuit.replace_block_with_op`

```python
from qiskit.circuit import CircuitInstruction

def collapser(dag, blocks, collapse_fn):
    """Collapser"""

    global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits)}
    global_index_map.update({wire: idx for idx, wire in enumerate(dag.clbits)})

    for block in blocks:
        # Find the set of qubits used in this block (which might be much smaller than
        # the set of all qubits).
        cur_qubits = set()
        cur_clbits = set()
        for node in block:
            cur_qubits.update(node.qargs)
            cur_clbits.update(node.cargs)

        # For reproducibility, order these qubits compatibly with the global order.
        sorted_qubits = sorted(cur_qubits, key=lambda x: global_index_map[x])
        sorted_clbits = sorted(cur_clbits, key=lambda x: global_index_map[x])

        # Construct a quantum circuit from the nodes in the block, remapping the qubits.
        wire_pos_map = dict((qb, ix) for ix, qb in enumerate(sorted_qubits))
        wire_pos_map.update(dict((qb, ix) for ix, qb in enumerate(sorted_clbits)))

        qc = QuantumCircuit(len(cur_qubits), len(cur_clbits))
        for node in block:
            remapped_qubits = [wire_pos_map[qarg] for qarg in node.qargs]
            remapped_clbits = [wire_pos_map[carg] for carg in node.cargs]
            qc.append(CircuitInstruction(node.op, remapped_qubits, remapped_clbits))

        # Collapse this quantum circuit into an operation.
        op = collapse_fn(qc)

        # Replace the block of nodes in the DAG by the constructed operation
        # (the function replace_block_with_op is implemented both in DAGCircuit and DAGDependency).
        dag.replace_block_with_op(block, op, wire_pos_map, cycle_check=False)

    return dag
```

and it seems to work if I draw the DAG:

![Screen Shot 2023-03-17 at 10 46 07 AM](https://user-images.githubusercontent.com/2235104/225942960-caf67a79-16ee-4a8a-8ef0-708a1623ce62.png)

But calling `dag_to_circuit` on the output returns a circuit without connection to clbits on the collapsed op:
```
        ┌───┐ ░ ┌────────────┐
   q_0: ┤ H ├─░─┤0           ├
        ├───┤ ░ │            │
   q_1: ┤ H ├─░─┤1 COLLAPSED ├
        ├───┤ ░ │            │
   q_2: ┤ H ├─░─┤2           ├
        └───┘ ░ └────────────┘
meas: 3/══════════════════════
                              
```
which if i try and transpile or decompose gives error: DAGCircuitError: 'bit mapping invalid: expected 3, got 6'.

I dont know if this is an issue with my attempt to modify the function, with `replace_block_with_op`, or with `dag_to_circuit`.


Discussion:
Full error trace for `BlockCollapser(dag).collapse_to_operation(blocks_meas, _collapse_fn)`:
```python
---------------------------------------------------------------------------
CircuitError                              Traceback (most recent call last)
Cell In [57], line 25
     22     return op
     24 # Collapse measure blocks to a single instruction
---> 25 BlockCollapser(dag).collapse_to_operation(blocks_meas, _collapse_fn)

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/dagcircuit/collect_blocks.py:282, in BlockCollapser.collapse_to_operation(self, blocks, collapse_fn)
    280 for node in block:
    281     remapped_qubits = [wire_pos_map[qarg] for qarg in node.qargs]
--> 282     qc.append(CircuitInstruction(node.op, remapped_qubits, node.cargs))
    284 # Collapse this quantum circuit into an operation.
    285 op = collapse_fn(qc)

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/circuit/quantumcircuit.py:1254, in QuantumCircuit.append(self, instruction, qargs, cargs)
   1251         operation = copy.deepcopy(operation)
   1253 expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []]
-> 1254 expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []]
   1256 if self._control_flow_scopes:
   1257     appender = self._control_flow_scopes[-1].append

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/circuit/quantumcircuit.py:1254, in <listcomp>(.0)
   1251         operation = copy.deepcopy(operation)
   1253 expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []]
-> 1254 expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []]
   1256 if self._control_flow_scopes:
   1257     appender = self._control_flow_scopes[-1].append

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/circuit/quantumcircuit.py:1148, in QuantumCircuit.cbit_argument_conversion(self, clbit_representation)
   1137 def cbit_argument_conversion(self, clbit_representation: ClbitSpecifier) -> List[Clbit]:
   1138     """
   1139     Converts several classical bit representations (such as indexes, range, etc.)
   1140     into a list of classical bits.
   (...)
   1146         List(tuple): Where each tuple is a classical bit.
   1147     """
-> 1148     return _bit_argument_conversion(
   1149         clbit_representation, self.clbits, self._clbit_indices, Clbit
   1150     )

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/circuit/quantumcircuit.py:4916, in _bit_argument_conversion(specifier, bit_sequence, bit_set, type_)
   4914     if specifier in bit_set:
   4915         return [specifier]
-> 4916     raise CircuitError(f"Bit '{specifier}' is not in the circuit.")
   4917 if isinstance(specifier, (int, np.integer)):
   4918     try:

CircuitError: "Bit 'Clbit(ClassicalRegister(3, 'meas'), 2)' is not in the circuit."
```
Full error trace for 
```python
circ = dag_to_circuit(collapser(dag, blocks_meas, _collapse_fn))
circ.decompose()
```
```python
---------------------------------------------------------------------------
DAGCircuitError                           Traceback (most recent call last)
Cell In [65], line 2
      1 circ = dag_to_circuit(collapser(dag, blocks_meas, _collapse_fn))
----> 2 circ.decompose()

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/circuit/quantumcircuit.py:1567, in QuantumCircuit.decompose(self, gates_to_decompose, reps)
   1565 pass_ = Decompose(gates_to_decompose)
   1566 for _ in range(reps):
-> 1567     dag = pass_.run(dag)
   1568 return dag_to_circuit(dag)

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/transpiler/passes/basis/decompose.py:65, in Decompose.run(self, dag)
     63         else:
     64             decomposition = circuit_to_dag(node.op.definition)
---> 65             dag.substitute_node_with_dag(node, decomposition)
     67 return dag

File ~/mambaforge/envs/pec/lib/python3.9/site-packages/qiskit/dagcircuit/dagcircuit.py:1203, in DAGCircuit.substitute_node_with_dag(self, node, input_dag, wires, propagate_condition)
   1197     node_wire_order += [
   1198         bit
   1199         for bit in self._bits_in_condition(getattr(node.op, "condition", None))
   1200         if bit not in node_cargs
   1201     ]
   1202 if len(wires) != len(node_wire_order):
-> 1203     raise DAGCircuitError(
   1204         f"bit mapping invalid: expected {len(node_wire_order)}, got {len(wires)}"
   1205     )
   1206 wire_map = dict(zip(wires, node_wire_order))
   1207 if len(wire_map) != len(node_wire_order):

DAGCircuitError: 'bit mapping invalid: expected 3, got 6'
```

This line seems to be culprit for the custom function I wrote (which itself could be a fix for BlockCollapser): https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/dagcircuit/dagcircuit.py#L1137-L1138

It seems cargs I being thrown away for everything that isn't a conditional op. If I change
```python
if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None):
    block_cargs |= set(nd.cargs)
```
to 
```python
block_cargs |= set(nd.cargs)
```
it seems to work for my use case, though I dont know if there are any other unintended concequences

Thanks @chriseclectic! 

Indeed, the `BlockCollapser` code that I wrote does not treat `clbits` correctly, and your fix above is perfect.

And indeed, the `dag_to_circuit` code
```
if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None):
    block_cargs |= set(nd.cargs)
```
seems to incorrectly drop `clbits` for non-conditional ops. It seems that I was the last to change these particular lines as well, however this behavior also existed in the code before my changes 
https://github.com/Qiskit/qiskit-terra/blob/ea0266769802a57de2ca823b1b996b668ec81178/qiskit/dagcircuit/dagcircuit.py#L1084-L1087,
again your fix seems perfect, though let me double-check with @jakelishman or @mtreinish if there might be a reason to have this check in the code.

Out of curiosity, how are you planning to use the "COLLAPSED" blocks, I guess `circ.decompose()` would just result in the original circuit? 



Resolving patch:
diff --git a/qiskit/dagcircuit/collect_blocks.py b/qiskit/dagcircuit/collect_blocks.py
--- a/qiskit/dagcircuit/collect_blocks.py
+++ b/qiskit/dagcircuit/collect_blocks.py
@@ -14,7 +14,8 @@
 """Various ways to divide a DAG into blocks of nodes, to split blocks of nodes
 into smaller sub-blocks, and to consolidate blocks."""
 
-from qiskit.circuit import QuantumCircuit, CircuitInstruction
+from qiskit.circuit import QuantumCircuit, CircuitInstruction, ClassicalRegister
+from qiskit.circuit.controlflow.condition import condition_bits
 from . import DAGOpNode, DAGCircuit, DAGDependency
 from .exceptions import DAGCircuitError
 
@@ -254,22 +255,46 @@ def collapse_to_operation(self, blocks, collapse_fn):
         then uses collapse_fn to collapse this circuit into a single operation.
         """
         global_index_map = {wire: idx for idx, wire in enumerate(self.dag.qubits)}
+        global_index_map.update({wire: idx for idx, wire in enumerate(self.dag.clbits)})
+
         for block in blocks:
-            # Find the set of qubits used in this block (which might be much smaller than
-            # the set of all qubits).
+            # Find the sets of qubits/clbits used in this block (which might be much smaller
+            # than the set of all qubits/clbits).
             cur_qubits = set()
+            cur_clbits = set()
+
+            # Additionally, find the set of classical registers used in conditions over full registers
+            # (in such a case, we need to add that register to the block circuit, not just its clbits).
+            cur_clregs = []
+
             for node in block:
                 cur_qubits.update(node.qargs)
-
-            # For reproducibility, order these qubits compatibly with the global order.
+                cur_clbits.update(node.cargs)
+                cond = getattr(node.op, "condition", None)
+                if cond is not None:
+                    cur_clbits.update(condition_bits(cond))
+                    if isinstance(cond[0], ClassicalRegister):
+                        cur_clregs.append(cond[0])
+
+            # For reproducibility, order these qubits/clbits compatibly with the global order.
             sorted_qubits = sorted(cur_qubits, key=lambda x: global_index_map[x])
+            sorted_clbits = sorted(cur_clbits, key=lambda x: global_index_map[x])
+
+            qc = QuantumCircuit(sorted_qubits, sorted_clbits)
+
+            # Add classical registers used in conditions over registers
+            for reg in cur_clregs:
+                qc.add_register(reg)
 
             # Construct a quantum circuit from the nodes in the block, remapping the qubits.
             wire_pos_map = {qb: ix for ix, qb in enumerate(sorted_qubits)}
-            qc = QuantumCircuit(len(cur_qubits))
+            wire_pos_map.update({qb: ix for ix, qb in enumerate(sorted_clbits)})
+
             for node in block:
-                remapped_qubits = [wire_pos_map[qarg] for qarg in node.qargs]
-                qc.append(CircuitInstruction(node.op, remapped_qubits, node.cargs))
+                instructions = qc.append(CircuitInstruction(node.op, node.qargs, node.cargs))
+                cond = getattr(node.op, "condition", None)
+                if cond is not None:
+                    instructions.c_if(*cond)
 
             # Collapse this quantum circuit into an operation.
             op = collapse_fn(qc)
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -30,6 +30,7 @@
 import rustworkx as rx
 
 from qiskit.circuit import ControlFlowOp, ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp
+from qiskit.circuit.controlflow.condition import condition_bits
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.circuit.quantumregister import QuantumRegister, Qubit
 from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
@@ -1131,8 +1132,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True):
 
         for nd in node_block:
             block_qargs |= set(nd.qargs)
-            if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None):
-                block_cargs |= set(nd.cargs)
+            block_cargs |= set(nd.cargs)
+            cond = getattr(nd.op, "condition", None)
+            if cond is not None:
+                block_cargs.update(condition_bits(cond))
 
         # Create replacement node
         new_node = DAGOpNode(
diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py
--- a/qiskit/dagcircuit/dagdependency.py
+++ b/qiskit/dagcircuit/dagdependency.py
@@ -19,6 +19,7 @@
 
 import rustworkx as rx
 
+from qiskit.circuit.controlflow.condition import condition_bits
 from qiskit.circuit.quantumregister import QuantumRegister, Qubit
 from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
 from qiskit.dagcircuit.exceptions import DAGDependencyError
@@ -394,11 +395,8 @@ def _create_op_node(self, operation, qargs, cargs):
                 #   (1) cindices_list are specific to template optimization and should not be computed
                 #       in this place.
                 #   (2) Template optimization pass needs currently does not handle general conditions.
-                if isinstance(operation.condition[0], Clbit):
-                    condition_bits = [operation.condition[0]]
-                else:
-                    condition_bits = operation.condition[0]
-                cindices_list = [self.clbits.index(clbit) for clbit in condition_bits]
+                cond_bits = condition_bits(operation.condition)
+                cindices_list = [self.clbits.index(clbit) for clbit in cond_bits]
             else:
                 cindices_list = []
         else:
@@ -591,8 +589,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True):
 
         for nd in node_block:
             block_qargs |= set(nd.qargs)
-            if nd.op.condition:
-                block_cargs |= set(nd.cargs)
+            block_cargs |= set(nd.cargs)
+            cond = getattr(nd.op, "condition", None)
+            if cond is not None:
+                block_cargs.update(condition_bits(cond))
 
         # Create replacement node
         new_node = self._create_op_node(


Test patch:
diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py
--- a/test/python/dagcircuit/test_collect_blocks.py
+++ b/test/python/dagcircuit/test_collect_blocks.py
@@ -15,10 +15,17 @@
 
 import unittest
 
-from qiskit.converters import circuit_to_dag, circuit_to_dagdependency
+from qiskit import QuantumRegister, ClassicalRegister
+from qiskit.converters import (
+    circuit_to_dag,
+    circuit_to_dagdependency,
+    circuit_to_instruction,
+    dag_to_circuit,
+    dagdependency_to_circuit,
+)
 from qiskit.test import QiskitTestCase
-from qiskit.circuit import QuantumCircuit
-from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter
+from qiskit.circuit import QuantumCircuit, Measure, Clbit
+from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter, BlockCollapser
 
 
 class TestCollectBlocks(QiskitTestCase):
@@ -449,6 +456,317 @@ def test_do_not_split_blocks(self):
         )
         self.assertEqual(len(blocks), 1)
 
+    def test_collect_blocks_with_cargs(self):
+        """Test collecting and collapsing blocks with classical bits appearing as cargs."""
+
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.h(1)
+        qc.h(2)
+        qc.measure_all()
+
+        dag = circuit_to_dag(qc)
+
+        # Collect all measure instructions
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of 3 measures
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 3)
+        self.assertEqual(blocks[0][0].op, Measure())
+        self.assertEqual(blocks[0][1].op, Measure())
+        self.assertEqual(blocks[0][2].op, Measure())
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dag_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 5)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[1].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[2].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[3].operation.name, "barrier")
+        self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3)
+        self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3)
+
+    def test_collect_blocks_with_cargs_dagdependency(self):
+        """Test collecting and collapsing blocks with classical bits appearing as cargs,
+        using DAGDependency."""
+
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.h(1)
+        qc.h(2)
+        qc.measure_all()
+
+        dag = circuit_to_dagdependency(qc)
+
+        # Collect all measure instructions
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of 3 measures
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 3)
+        self.assertEqual(blocks[0][0].op, Measure())
+        self.assertEqual(blocks[0][1].op, Measure())
+        self.assertEqual(blocks[0][2].op, Measure())
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dagdependency_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 5)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[1].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[2].operation.name, "h")
+        self.assertEqual(collapsed_qc.data[3].operation.name, "barrier")
+        self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3)
+        self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3)
+
+    def test_collect_blocks_with_clbits(self):
+        """Test collecting and collapsing blocks with classical bits appearing under
+        condition."""
+
+        qc = QuantumCircuit(4, 3)
+        qc.cx(0, 1).c_if(0, 1)
+        qc.cx(2, 3)
+        qc.cx(1, 2)
+        qc.cx(0, 1)
+        qc.cx(2, 3).c_if(1, 0)
+
+        dag = circuit_to_dag(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 5)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dag_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2)
+
+    def test_collect_blocks_with_clbits_dagdependency(self):
+        """Test collecting and collapsing blocks with classical bits appearing
+        under conditions, using DAGDependency."""
+
+        qc = QuantumCircuit(4, 3)
+        qc.cx(0, 1).c_if(0, 1)
+        qc.cx(2, 3)
+        qc.cx(1, 2)
+        qc.cx(0, 1)
+        qc.cx(2, 3).c_if(1, 0)
+
+        dag = circuit_to_dagdependency(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 5)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dagdependency_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2)
+
+    def test_collect_blocks_with_clbits2(self):
+        """Test collecting and collapsing blocks with classical bits appearing under
+        condition."""
+
+        qreg = QuantumRegister(4, "qr")
+        creg = ClassicalRegister(3, "cr")
+        cbit = Clbit()
+
+        qc = QuantumCircuit(qreg, creg, [cbit])
+        qc.cx(0, 1).c_if(creg[1], 1)
+        qc.cx(2, 3).c_if(cbit, 0)
+        qc.cx(1, 2)
+        qc.cx(0, 1).c_if(creg[2], 1)
+
+        dag = circuit_to_dag(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 4)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dag_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3)
+
+    def test_collect_blocks_with_clbits2_dagdependency(self):
+        """Test collecting and collapsing blocks with classical bits appearing under
+        condition, using DAGDependency."""
+
+        qreg = QuantumRegister(4, "qr")
+        creg = ClassicalRegister(3, "cr")
+        cbit = Clbit()
+
+        qc = QuantumCircuit(qreg, creg, [cbit])
+        qc.cx(0, 1).c_if(creg[1], 1)
+        qc.cx(2, 3).c_if(cbit, 0)
+        qc.cx(1, 2)
+        qc.cx(0, 1).c_if(creg[2], 1)
+
+        dag = circuit_to_dag(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 4)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dag_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3)
+
+    def test_collect_blocks_with_cregs(self):
+        """Test collecting and collapsing blocks with classical registers appearing under
+        condition."""
+
+        qreg = QuantumRegister(4, "qr")
+        creg = ClassicalRegister(3, "cr")
+        creg2 = ClassicalRegister(2, "cr2")
+
+        qc = QuantumCircuit(qreg, creg, creg2)
+        qc.cx(0, 1).c_if(creg, 3)
+        qc.cx(1, 2)
+        qc.cx(0, 1).c_if(creg[2], 1)
+
+        dag = circuit_to_dag(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 3)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dag_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3)
+
+    def test_collect_blocks_with_cregs_dagdependency(self):
+        """Test collecting and collapsing blocks with classical registers appearing under
+        condition, using DAGDependency."""
+
+        qreg = QuantumRegister(4, "qr")
+        creg = ClassicalRegister(3, "cr")
+        creg2 = ClassicalRegister(2, "cr2")
+
+        qc = QuantumCircuit(qreg, creg, creg2)
+        qc.cx(0, 1).c_if(creg, 3)
+        qc.cx(1, 2)
+        qc.cx(0, 1).c_if(creg[2], 1)
+
+        dag = circuit_to_dagdependency(qc)
+
+        # Collect all cx gates (including the conditional ones)
+        blocks = BlockCollector(dag).collect_all_matching_blocks(
+            lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1
+        )
+
+        # We should have a single block consisting of all CX nodes
+        self.assertEqual(len(blocks), 1)
+        self.assertEqual(len(blocks[0]), 3)
+
+        def _collapse_fn(circuit):
+            op = circuit_to_instruction(circuit)
+            op.name = "COLLAPSED"
+            return op
+
+        # Collapse block with measures into a single "COLLAPSED" block
+        dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn)
+        collapsed_qc = dagdependency_to_circuit(dag)
+
+        self.assertEqual(len(collapsed_qc.data), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED")
+        self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3)
+        self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3)
+
 
 if __name__ == "__main__":
     unittest.main()