Repository: Qiskit/qiskit

Issue:
RemoveFinalMeasurement scales superlinearly with gates
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

The RemoveFinalMeasurement pass scales superlinearly with the number of gates in a circuit. For a randomly generated circuit of equal width and depth this pass scales as width^n where n ~ 2.8
![image](https://user-images.githubusercontent.com/25756217/133840463-6d3c5aa4-b37d-4014-a117-c04ed39a28e1.png)

![image](https://user-images.githubusercontent.com/25756217/133840387-a3c28be1-3cd5-4c28-8b7a-597bf6563d6c.png)

### What is the expected enhancement?
It seems like the scaling could be <= 2. Since the number of final measurements can be at most the number of qubits, this might be closer to 1.




Discussion:
Yeah, looking at the pass code it's going about it pretty inefficiently. It's going over every node in the graph and checking that it's children are output nodes to find final operations:

https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/utils/remove_final_measurements.py#L43-L55

When instead it should be just looping over the parents of the output qubit nodes and checking for a a barrier or measurement explicitly.

Also for something that's at most removing n (for n qubits) nodes from the graph it goes about creating the output by iterating over the full dag and recreating it: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/utils/remove_final_measurements.py#L80-L86

It would probably be better to just remove the nodes in place.

Resolving patch:
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -786,17 +786,30 @@ def idle_wires(self, ignore=None):
 
         Yields:
             Bit: Bit in idle wire.
+
+        Raises:
+            DAGCircuitError: If the DAG is invalid
         """
         if ignore is None:
-            ignore = []
+            ignore = set()
+        ignore_set = set(ignore)
         for wire in self._wires:
-            nodes = [
-                node
-                for node in self.nodes_on_wire(wire, only_ops=True)
-                if node.op.name not in ignore
-            ]
-            if len(nodes) == 0:
-                yield wire
+            if not ignore:
+                try:
+                    child = next(self.successors(self.input_map[wire]))
+                except StopIteration as e:
+                    raise DAGCircuitError(
+                        "Invalid dagcircuit input node %s has no output" % self.input_map[wire]
+                    ) from e
+                if isinstance(child, DAGOutNode):
+                    yield wire
+            else:
+                for node in self.nodes_on_wire(wire, only_ops=True):
+                    if node.op.name not in ignore_set:
+                        # If we found an op node outside of ignore we can stop iterating over the wire
+                        break
+                else:
+                    yield wire
 
     def size(self):
         """Return the number of operations."""
diff --git a/qiskit/transpiler/passes/utils/remove_final_measurements.py b/qiskit/transpiler/passes/utils/remove_final_measurements.py
--- a/qiskit/transpiler/passes/utils/remove_final_measurements.py
+++ b/qiskit/transpiler/passes/utils/remove_final_measurements.py
@@ -34,25 +34,16 @@ def run(self, dag):
         Returns:
             DAGCircuit: the optimized DAG.
         """
-        final_op_types = ["measure", "barrier"]
+        final_op_types = {"measure", "barrier"}
         final_ops = []
         cregs_to_remove = dict()
         clbits_with_final_measures = set()
         clbit_registers = {clbit: creg for creg in dag.cregs.values() for clbit in creg}
 
-        for candidate_node in dag.named_nodes(*final_op_types):
-            is_final_op = True
-
-            for _, child_successors in dag.bfs_successors(candidate_node):
-                if any(
-                    isinstance(suc, DAGOpNode) and suc.name not in final_op_types
-                    for suc in child_successors
-                ):
-                    is_final_op = False
-                    break
-
-            if is_final_op:
-                final_ops.append(candidate_node)
+        for qubit in dag.qubits:
+            op_node = next(dag.predecessors(dag.output_map[qubit]))
+            if isinstance(op_node, DAGOpNode) and op_node.op.name in final_op_types:
+                final_ops.append(op_node)
 
         if not final_ops:
             return dag
@@ -77,10 +68,4 @@ def run(self, dag):
             if val in cregs_to_remove and cregs_to_remove[val] == val.size:
                 del dag.cregs[key]
 
-        new_dag = dag._copy_circuit_metadata()
-
-        for node in dag.topological_op_nodes():
-            # copy the condition over too
-            new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs)
-
-        return new_dag
+        return dag


Test patch: