Skip to content

DAGCircuit.substitute_node_with_dag mutates input_dag if node has a condition #4727

@kdk

Description

@kdk

Information

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions