Repository: Qiskit/qiskit

Issue:
DAGCircuit.substitute_node_with_dag mutates input_dag if node has a condition
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 63449c4

### What is the current behavior?

From https://travis-ci.com/github/Qiskit/qiskit-terra/jobs/361028025#L6256 , `DAGCircuit.substitute_node_with_dag(node, input_dag)` will mutate `input_dag` to add the condition from `node`. This will lead to issues of `input_dag` is later re-used in a non-conditional context.

### Steps to reproduce the problem

```
>>> import qiskit as qk
>>> qc = qk.QuantumCircuit(2,1)
>>> qc.cx(0,1).c_if(qc.cregs[0], 1)
>>> qc.cx(1,0)
>>> print(qc)
            ┌───┐
q_0: ───■───┤ X ├
      ┌─┴─┐ └─┬─┘
q_1: ─┤ X ├───■──
      └─┬─┘      
     ┌──┴──┐     
c: 1/╡ = 1 ╞═════
     └─────┘     
>>> qk.transpile(qc, basis_gates=['u3', 'cz'])

---------------------------------------------------------------------------
DAGCircuitError                           Traceback (most recent call last)
<ipython-input-2-ee46705662ef> in <module>
      4 
      5 
----> 6 qk.transpile(qc, basis_gates=['u3', 'cz']).draw()

~/q/qiskit-terra/qiskit/compiler/transpile.py in transpile(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, translation_method, seed_transpiler, optimization_level, pass_manager, callback, output_name)
    217     # Transpile circuits in parallel
    218     circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_args)),
--> 219                             task_args=(faulty_qubits_map, backend))
    220 
    221     if len(circuits) == 1:

~/q/qiskit-terra/qiskit/tools/parallel.py in parallel_map(task, values, task_args, task_kwargs, num_processes)
    106         return []
    107     if len(values) == 1:
--> 108         return [task(values[0], *task_args, **task_kwargs)]
    109 
    110     Publisher().publish("terra.parallel.start", len(values))

~/q/qiskit-terra/qiskit/compiler/transpile.py in _transpile_circuit(circuit_config_tuple, faulty_qubits_map, backend)
    320 
    321     result = pass_manager.run(circuit, callback=transpile_config['callback'],
--> 322                               output_name=transpile_config['output_name'])
    323 
    324     if faulty_qubits_map:

~/q/qiskit-terra/qiskit/transpiler/passmanager.py in run(self, circuits, output_name, callback)
    212         """
    213         if isinstance(circuits, QuantumCircuit):
--> 214             return self._run_single_circuit(circuits, output_name, callback)
    215         elif len(circuits) == 1:
    216             return self._run_single_circuit(circuits[0], output_name, callback)

~/q/qiskit-terra/qiskit/transpiler/passmanager.py in _run_single_circuit(self, circuit, output_name, callback)
    275         if callback is None and self.callback:  # TODO to remove with __init__(callback)
    276             callback = self.callback
--> 277         result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
    278         self.property_set = running_passmanager.property_set
    279         return result

~/q/qiskit-terra/qiskit/transpiler/runningpassmanager.py in run(***failed resolving arguments***)
    113         for passset in self.working_list:
    114             for pass_ in passset:
--> 115                 dag = self._do_pass(pass_, dag, passset.options)
    116 
    117         circuit = dag_to_circuit(dag)

~/q/qiskit-terra/qiskit/transpiler/runningpassmanager.py in _do_pass(self, pass_, dag, options)
    143         # Run the pass itself, if not already run
    144         if pass_ not in self.valid_passes:
--> 145             dag = self._run_this_pass(pass_, dag)
    146 
    147             # update the valid_passes property

~/q/qiskit-terra/qiskit/transpiler/runningpassmanager.py in _run_this_pass(self, pass_, dag)
    155             # Measure time if we have a callback or logging set
    156             start_time = time()
--> 157             new_dag = pass_.run(dag)
    158             end_time = time()
    159             run_time = end_time - start_time

~/q/qiskit-terra/qiskit/transpiler/passes/basis/basis_translator.py in run(self, dag)
    153                     dag.substitute_node(node, bound_target_dag.op_nodes()[0].op, inplace=True)
    154                 else:
--> 155                     dag.substitute_node_with_dag(node, bound_target_dag)
    156             else:
    157                 raise TranspilerError('BasisTranslator did not map {}.'.format(node.name))

~/q/qiskit-terra/qiskit/dagcircuit/dagcircuit.py in substitute_node_with_dag(self, node, input_dag, wires)
    810             wires = input_dag.wires
    811 
--> 812         self._check_wires_list(wires, node)
    813 
    814         # Create a proxy wire_map to identify fragments and duplicates

~/q/qiskit-terra/qiskit/dagcircuit/dagcircuit.py in _check_wires_list(self, wires, node)
    686         if len(wires) != wire_tot:
    687             raise DAGCircuitError("expected %d wires, got %d"
--> 688                                   % (wire_tot, len(wires)))
    689 
    690     def _make_pred_succ_maps(self, node):

DAGCircuitError: 'expected 2 wires, got 3'

```

### What is the expected behavior?



### Suggested solutions

Around https://github.com/Qiskit/qiskit-terra/blob/700beea/qiskit/dagcircuit/dagcircuit.py#L796 , we should make a copy of `input_dag` if it needs to be modified prior to insertion.



Discussion:

Resolving patch:
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -789,25 +789,27 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
         Raises:
             DAGCircuitError: if met with unexpected predecessor/successors
         """
+        in_dag = input_dag
         condition = node.condition
         # the dag must be amended if used in a
         # conditional context. delete the op nodes and replay
         # them with the condition.
         if condition:
-            input_dag.add_creg(condition[0])
+            in_dag = copy.deepcopy(input_dag)
+            in_dag.add_creg(condition[0])
             to_replay = []
-            for sorted_node in input_dag.topological_nodes():
+            for sorted_node in in_dag.topological_nodes():
                 if sorted_node.type == "op":
                     sorted_node.op.condition = condition
                     to_replay.append(sorted_node)
-            for input_node in input_dag.op_nodes():
-                input_dag.remove_op_node(input_node)
+            for input_node in in_dag.op_nodes():
+                in_dag.remove_op_node(input_node)
             for replay_node in to_replay:
-                input_dag.apply_operation_back(replay_node.op, replay_node.qargs,
-                                               replay_node.cargs)
+                in_dag.apply_operation_back(replay_node.op, replay_node.qargs,
+                                            replay_node.cargs)
 
         if wires is None:
-            wires = input_dag.wires
+            wires = in_dag.wires
 
         self._check_wires_list(wires, node)
 
@@ -815,13 +817,13 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
         # and determine what registers need to be added to self
         proxy_map = {w: QuantumRegister(1, 'proxy') for w in wires}
         add_qregs = self._check_edgemap_registers(proxy_map,
-                                                  input_dag.qregs,
+                                                  in_dag.qregs,
                                                   {}, False)
         for qreg in add_qregs:
             self.add_qreg(qreg)
 
         add_cregs = self._check_edgemap_registers(proxy_map,
-                                                  input_dag.cregs,
+                                                  in_dag.cregs,
                                                   {}, False)
         for creg in add_cregs:
             self.add_creg(creg)
@@ -840,7 +842,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
         self._check_wiremap_validity(wire_map, wires, self.input_map)
         pred_map, succ_map = self._make_pred_succ_maps(node)
         full_pred_map, full_succ_map = self._full_pred_succ_maps(pred_map, succ_map,
-                                                                 input_dag, wire_map)
+                                                                 in_dag, wire_map)
 
         if condition_bit_list:
             # If we are replacing a conditional node, map input dag through
@@ -848,7 +850,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
             # bits.
             condition_bits = set(condition_bit_list)
 
-            for op_node in input_dag.op_nodes():
+            for op_node in in_dag.op_nodes():
                 mapped_cargs = {wire_map[carg] for carg in op_node.cargs}
 
                 if condition_bits & mapped_cargs:
@@ -859,7 +861,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None):
         self._multi_graph.remove_node(node._node_id)
 
         # Iterate over nodes of input_circuit
-        for sorted_node in input_dag.topological_op_nodes():
+        for sorted_node in in_dag.topological_op_nodes():
             # Insert a new node
             condition = self._map_condition(wire_map, sorted_node.condition)
             m_qargs = list(map(lambda x: wire_map.get(x, x),


Test patch:
diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py
--- a/test/python/transpiler/test_preset_passmanagers.py
+++ b/test/python/transpiler/test_preset_passmanagers.py
@@ -470,3 +470,11 @@ def test_optimization_condition(self, level):
         backend = FakeJohannesburg()
         circ = transpile(qc, backend, optimization_level=level)
         self.assertIsInstance(circ, QuantumCircuit)
+
+    def test_input_dag_copy(self):
+        """Test substitute_node_with_dag input_dag copy on condition"""
+        qc = QuantumCircuit(2, 1)
+        qc.cx(0, 1).c_if(qc.cregs[0], 1)
+        qc.cx(1, 0)
+        circ = transpile(qc, basis_gates=['u3', 'cz'])
+        self.assertIsInstance(circ, QuantumCircuit)