diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py index eb012ad38336..76541d8c89df 100644 --- a/qiskit/converters/ast_to_dag.py +++ b/qiskit/converters/ast_to_dag.py @@ -236,11 +236,11 @@ def _process_cnot(self, node): cx_gate = std.CXGate() cx_gate.condition = self.condition if len(id0) > 1 and len(id1) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], []) + self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [], check=False) elif len(id0) > 1: - self.dag.apply_operation_back(cx_gate, [id0[idx], id1[0]], []) + self.dag.apply_operation_back(cx_gate, [id0[idx], id1[0]], [], check=False) else: - self.dag.apply_operation_back(cx_gate, [id0[0], id1[idx]], []) + self.dag.apply_operation_back(cx_gate, [id0[0], id1[idx]], [], check=False) def _process_measure(self, node): """Process a measurement node.""" @@ -253,7 +253,7 @@ def _process_measure(self, node): for idx, idy in zip(id0, id1): meas_gate = Measure() meas_gate.condition = self.condition - self.dag.apply_operation_back(meas_gate, [idx], [idy]) + self.dag.apply_operation_back(meas_gate, [idx], [idy], check=False) def _process_if(self, node): """Process an if node.""" @@ -335,14 +335,14 @@ def _process_node(self, node): for qubit in ids: for j, _ in enumerate(qubit): qubits.append(qubit[j]) - self.dag.apply_operation_back(Barrier(len(qubits)), qubits, []) + self.dag.apply_operation_back(Barrier(len(qubits)), qubits, [], check=False) elif node.type == "reset": id0 = self._process_bit_id(node.children[0]) for i, _ in enumerate(id0): reset = Reset() reset.condition = self.condition - self.dag.apply_operation_back(reset, [id0[i]], []) + self.dag.apply_operation_back(reset, [id0[i]], [], check=False) elif node.type == "if": self._process_if(node) @@ -399,7 +399,7 @@ def _create_dag_op(self, name, params, qargs): """ op = self._create_op(name, params) op.condition = self.condition - self.dag.apply_operation_back(op, qargs, []) + self.dag.apply_operation_back(op, qargs, [], check=False) def _create_op(self, name, params): if name in self.standard_extension: diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py index a7410f3e7698..0c80fa702762 100644 --- a/qiskit/converters/circuit_to_dag.py +++ b/qiskit/converters/circuit_to_dag.py @@ -89,7 +89,7 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord op = instruction.operation if copy_operations: op = copy.deepcopy(op) - dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits) + dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits, check=False) dagcircuit.duration = circuit.duration dagcircuit.unit = circuit.unit diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index ef2cdbb93422..8a4be72494e2 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -22,7 +22,6 @@ """ from collections import OrderedDict, defaultdict, deque, namedtuple import copy -import itertools import math from typing import Dict, Generator, Any, List @@ -569,6 +568,7 @@ def _bits_in_operation(operation): Returns: Iterable[Clbit]: the :class:`.Clbit`\\ s involved. """ + # If updating this, also update the fast-path checker `DAGCirucit._operation_may_have_bits`. if (condition := getattr(operation, "condition", None)) is not None: yield from condition_resources(condition).clbits if isinstance(operation, SwitchCaseOp): @@ -580,6 +580,22 @@ def _bits_in_operation(operation): else: yield from node_resources(target).clbits + @staticmethod + def _operation_may_have_bits(operation) -> bool: + """Return whether a given :class:`.Operation` may contain any :class:`.Clbit` instances + in itself (e.g. a control-flow operation). + + Args: + operation (qiskit.circuit.Operation): the operation to check. + """ + # This is separate to `_bits_in_operation` because most of the time there won't be any bits, + # so we want a fast path to be able to skip creating and testing a generator for emptiness. + # + # If updating this, also update `DAGCirucit._bits_in_operation`. + return getattr(operation, "condition", None) is not None or isinstance( + operation, SwitchCaseOp + ) + def _increment_op(self, op): if op.name in self._op_names: self._op_names[op.name] += 1 @@ -592,23 +608,6 @@ def _decrement_op(self, op): else: self._op_names[op.name] -= 1 - def _add_op_node(self, op, qargs, cargs): - """Add a new operation node to the graph and assign properties. - - Args: - op (qiskit.circuit.Operation): the operation associated with the DAG node - qargs (list[Qubit]): list of quantum wires to attach to. - cargs (list[Clbit]): list of classical wires to attach to. - Returns: - int: The integer node index for the new op node on the DAG - """ - # Add a new operation node to the graph - new_node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self) - node_index = self._multi_graph.add_node(new_node) - new_node._node_id = node_index - self._increment_op(op) - return node_index - @deprecate_func( additional_msg="Instead, use :meth:`~copy_empty_like()`, which acts identically.", since="0.20.0", @@ -647,13 +646,18 @@ def copy_empty_like(self): return target_dag - def apply_operation_back(self, op, qargs=(), cargs=()): + def apply_operation_back(self, op, qargs=(), cargs=(), *, check=True): """Apply an operation to the output of the circuit. Args: op (qiskit.circuit.Operation): the operation associated with the DAG node qargs (tuple[Qubit]): qubits that op will be applied to cargs (tuple[Clbit]): cbits that op will be applied to + check (bool): If ``True`` (default), this function will enforce that the + :class:`.DAGCircuit` data-structure invariants are maintained (all ``qargs`` are + :class:`.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* + uphold these invariants itself, but the cost of several checks will be skipped. + This is most useful when building a new DAG from a source of known-good nodes. Returns: DAGOpNode: the node for the op that was added to the dag @@ -664,52 +668,74 @@ def apply_operation_back(self, op, qargs=(), cargs=()): qargs = tuple(qargs) if qargs is not None else () cargs = tuple(cargs) if cargs is not None else () - all_cbits = set(self._bits_in_operation(op)).union(cargs) + if self._operation_may_have_bits(op): + # This is the slow path; most of the time, this won't happen. + all_cbits = set(self._bits_in_operation(op)).union(cargs) + else: + all_cbits = cargs - self._check_condition(op.name, getattr(op, "condition", None)) - self._check_bits(qargs, self.output_map) - self._check_bits(all_cbits, self.output_map) + if check: + self._check_condition(op.name, getattr(op, "condition", None)) + self._check_bits(qargs, self.output_map) + self._check_bits(all_cbits, self.output_map) - node_index = self._add_op_node(op, qargs, cargs) + node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self) + node._node_id = self._multi_graph.add_node(node) + self._increment_op(op) # Add new in-edges from predecessors of the output nodes to the # operation node while deleting the old in-edges of the output nodes # and adding new edges from the operation node to each output node - - al = [qargs, all_cbits] self._multi_graph.insert_node_on_in_edges_multiple( - node_index, [self.output_map[q]._node_id for q in itertools.chain(*al)] + node._node_id, + [self.output_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits], ) - return self._multi_graph[node_index] + return node - def apply_operation_front(self, op, qargs=(), cargs=()): + def apply_operation_front(self, op, qargs=(), cargs=(), *, check=True): """Apply an operation to the input of the circuit. Args: op (qiskit.circuit.Operation): the operation associated with the DAG node qargs (tuple[Qubit]): qubits that op will be applied to cargs (tuple[Clbit]): cbits that op will be applied to + check (bool): If ``True`` (default), this function will enforce that the + :class:`.DAGCircuit` data-structure invariants are maintained (all ``qargs`` are + :class:`.Qubit`\\ s, all are in the DAG, etc). If ``False``, the caller *must* + uphold these invariants itself, but the cost of several checks will be skipped. + This is most useful when building a new DAG from a source of known-good nodes. Returns: DAGOpNode: the node for the op that was added to the dag Raises: DAGCircuitError: if initial nodes connected to multiple out edges """ - all_cbits = set(self._bits_in_operation(op)).union(cargs) + qargs = tuple(qargs) if qargs is not None else () + cargs = tuple(cargs) if cargs is not None else () + + if self._operation_may_have_bits(op): + # This is the slow path; most of the time, this won't happen. + all_cbits = set(self._bits_in_operation(op)).union(cargs) + else: + all_cbits = cargs - self._check_condition(op.name, getattr(op, "condition", None)) - self._check_bits(qargs, self.input_map) - self._check_bits(all_cbits, self.input_map) - node_index = self._add_op_node(op, qargs, cargs) + if check: + self._check_condition(op.name, getattr(op, "condition", None)) + self._check_bits(qargs, self.input_map) + self._check_bits(all_cbits, self.input_map) + + node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self) + node._node_id = self._multi_graph.add_node(node) + self._increment_op(op) # Add new out-edges to successors of the input nodes from the # operation node while deleting the old out-edges of the input nodes # and adding new edges to the operation node from each input node - al = [qargs, all_cbits] self._multi_graph.insert_node_on_out_edges_multiple( - node_index, [self.input_map[q]._node_id for q in itertools.chain(*al)] + node._node_id, + [self.input_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits], ) - return self._multi_graph[node_index] + return node def compose(self, other, qubits=None, clbits=None, front=False, inplace=True): """Compose the ``other`` circuit onto the output of this circuit. @@ -819,7 +845,7 @@ def _reject_new_register(reg): op.condition = variable_mapper.map_condition(condition, allow_reorder=True) elif isinstance(op, SwitchCaseOp): op.target = variable_mapper.map_target(op.target) - dag.apply_operation_back(op, m_qargs, m_cargs) + dag.apply_operation_back(op, m_qargs, m_cargs, check=False) else: raise DAGCircuitError("bad node type %s" % type(nd)) @@ -1191,7 +1217,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit node_wire_order = list(node.qargs) + list(node.cargs) # If we're not propagating it, the number of wires in the input DAG should include the # condition as well. - if not propagate_condition: + if not propagate_condition and self._operation_may_have_bits(node.op): node_wire_order += [ bit for bit in self._bits_in_operation(node.op) if bit not in node_cargs ] @@ -1260,7 +1286,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit ) new_op = copy.copy(in_node.op) new_op.condition = new_condition - in_dag.apply_operation_back(new_op, in_node.qargs, in_node.cargs) + in_dag.apply_operation_back(new_op, in_node.qargs, in_node.cargs, check=False) else: in_dag = input_dag @@ -1483,7 +1509,7 @@ def _key(x): subgraph_is_classical = False if not isinstance(node, DAGOpNode): continue - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) # Ignore DAGs created for empty clbits if not subgraph_is_classical: @@ -1801,7 +1827,7 @@ def layers(self): for node in op_nodes: # this creates new DAGOpNodes in the new_layer - new_layer.apply_operation_back(node.op, node.qargs, node.cargs) + new_layer.apply_operation_back(node.op, node.qargs, node.cargs, check=False) # The quantum registers that have an operation in this layer. support_list = [ @@ -1829,7 +1855,7 @@ def serial_layers(self): cargs = copy.copy(next_node.cargs) # Add node to new_layer - new_layer.apply_operation_back(op, qargs, cargs) + new_layer.apply_operation_back(op, qargs, cargs, check=False) # Add operation to partition if not getattr(next_node.op, "_directive", False): support_list.append(list(qargs)) diff --git a/qiskit/quantum_info/synthesis/one_qubit_decompose.py b/qiskit/quantum_info/synthesis/one_qubit_decompose.py index ac83fa70ddf6..c4e05eea584b 100644 --- a/qiskit/quantum_info/synthesis/one_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/one_qubit_decompose.py @@ -167,7 +167,7 @@ def build_circuit(self, gates, global_phase): else: gate = gate_entry - dag.apply_operation_back(gate, [qr[0]]) + dag.apply_operation_back(gate, (qr[0],), check=False) return dag else: circuit = QuantumCircuit(qr, global_phase=global_phase) diff --git a/qiskit/synthesis/discrete_basis/gate_sequence.py b/qiskit/synthesis/discrete_basis/gate_sequence.py index 3d8907a97f69..611babcce964 100644 --- a/qiskit/synthesis/discrete_basis/gate_sequence.py +++ b/qiskit/synthesis/discrete_basis/gate_sequence.py @@ -114,18 +114,18 @@ def to_dag(self): """ from qiskit.dagcircuit import DAGCircuit - qreg = [Qubit()] + qreg = (Qubit(),) dag = DAGCircuit() dag.add_qubits(qreg) if len(self.gates) == 0 and not np.allclose(self.product, np.identity(3)): su2 = _convert_so3_to_su2(self.product) - dag.apply_operation_back(UnitaryGate(su2), qreg) + dag.apply_operation_back(UnitaryGate(su2), qreg, check=False) return dag dag.global_phase = self.global_phase for gate in self.gates: - dag.apply_operation_back(gate, qreg) + dag.apply_operation_back(gate, qreg, check=False) return dag diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 1aad7c0bb338..2501e0860f16 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -573,7 +573,7 @@ def _compose_transforms(basis_transforms, source_basis, source_dag): dag = DAGCircuit() qr = QuantumRegister(gate_num_qubits) dag.add_qreg(qr) - dag.apply_operation_back(placeholder_gate, qr[:], []) + dag.apply_operation_back(placeholder_gate, qr, (), check=False) mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms: diff --git a/qiskit/transpiler/passes/basis/translate_parameterized.py b/qiskit/transpiler/passes/basis/translate_parameterized.py index 9e6e555a4d81..9629132866dd 100644 --- a/qiskit/transpiler/passes/basis/translate_parameterized.py +++ b/qiskit/transpiler/passes/basis/translate_parameterized.py @@ -172,6 +172,6 @@ def _instruction_to_dag(op: Instruction) -> DAGCircuit: dag = DAGCircuit() dag.add_qubits([Qubit() for _ in range(op.num_qubits)]) dag.add_qubits([Clbit() for _ in range(op.num_clbits)]) - dag.apply_operation_back(op, dag.qubits, dag.clbits) + dag.apply_operation_back(op, dag.qubits, dag.clbits, check=False) return dag diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index a69d90f22163..b6b7282e304e 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -74,7 +74,7 @@ def run(self, dag): virtual_phsyical_map = layout.get_virtual_bits() for node in dag.topological_op_nodes(): qargs = [q[virtual_phsyical_map[qarg]] for qarg in node.qargs] - new_dag.apply_operation_back(node.op, qargs, node.cargs) + new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False) else: # First build a new layout object going from: # old virtual -> old phsyical -> new virtual -> new physical @@ -94,7 +94,7 @@ def run(self, dag): # Apply new layout to the circuit for node in dag.topological_op_nodes(): qargs = [q[new_virtual_to_physical[qarg]] for qarg in node.qargs] - new_dag.apply_operation_back(node.op, qargs, node.cargs) + new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False) self.property_set["layout"] = full_layout if (final_layout := self.property_set["final_layout"]) is not None: final_layout_mapping = { diff --git a/qiskit/transpiler/passes/layout/disjoint_utils.py b/qiskit/transpiler/passes/layout/disjoint_utils.py index 46f21d3bab9f..ffbb5076c1ef 100644 --- a/qiskit/transpiler/passes/layout/disjoint_utils.py +++ b/qiskit/transpiler/passes/layout/disjoint_utils.py @@ -112,7 +112,9 @@ def split_barriers(dag: DAGCircuit): split_dag.add_qubits([Qubit() for _ in range(num_qubits)]) for i in range(num_qubits): split_dag.apply_operation_back( - Barrier(1, label=barrier_uuid), qargs=[split_dag.qubits[i]] + Barrier(1, label=barrier_uuid), + qargs=(split_dag.qubits[i],), + check=False, ) dag.substitute_node_with_dag(node, split_dag) @@ -191,7 +193,7 @@ def separate_dag(dag: DAGCircuit) -> List[DAGCircuit]: new_dag.global_phase = 0 for node in dag.topological_op_nodes(): if dag_qubits.issuperset(node.qargs): - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) idle_clbits = [] for bit, node in new_dag.input_map.items(): succ_node = next(new_dag.successors(node)) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 6e740511a38a..854fe8204551 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -140,13 +140,13 @@ def _resynthesize_run(self, matrix, qubit=None): return best_synth_circuit def _gate_sequence_to_dag(self, best_synth_circuit): - qubits = [Qubit()] + qubits = (Qubit(),) out_dag = DAGCircuit() out_dag.add_qubits(qubits) out_dag.global_phase = best_synth_circuit.global_phase for gate_name, angles in best_synth_circuit: - out_dag.apply_operation_back(NAME_MAP[gate_name](*angles), qubits) + out_dag.apply_operation_back(NAME_MAP[gate_name](*angles), qubits, check=False) return out_dag def _substitution_checks(self, dag, old_run, new_circ, basis, qubit): diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 96cc3b8cdd62..54ebe4f41d0f 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -64,7 +64,7 @@ def run(self, dag): old_measure_qarg = successor.qargs[0] new_measure_qarg = swap_qargs[swap_qargs.index(old_measure_qarg) - 1] measure_layer.apply_operation_back( - Measure(), [new_measure_qarg], [successor.cargs[0]] + Measure(), (new_measure_qarg,), (successor.cargs[0],), check=False ) dag.compose(measure_layer) dag.remove_op_node(swap) diff --git a/qiskit/transpiler/passes/routing/algorithms/util.py b/qiskit/transpiler/passes/routing/algorithms/util.py index 930c835603dd..d18d22a0db96 100644 --- a/qiskit/transpiler/passes/routing/algorithms/util.py +++ b/qiskit/transpiler/passes/routing/algorithms/util.py @@ -98,6 +98,6 @@ def permutation_circuit(swaps: Iterable[list[Swap[_V]]]) -> PermutationCircuit: # Apply swaps to the circuit. for swap_step in swap_list: for swap0, swap1 in swap_step: - dag.apply_operation_back(SwapGate(), [inputmap[swap0], inputmap[swap1]]) + dag.apply_operation_back(SwapGate(), (inputmap[swap0], inputmap[swap1]), check=False) return PermutationCircuit(dag, inputmap) diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 65b22ef9c178..af721ee51c7d 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -100,7 +100,7 @@ def run(self, dag): # create the swap operation swap_layer.apply_operation_back( - SwapGate(), qargs=[qubit_1, qubit_2], cargs=[] + SwapGate(), (qubit_1, qubit_2), cargs=(), check=False ) # layer insertion diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 485ea76a8b50..0893736a2aca 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -176,7 +176,7 @@ def run(self, dag): # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. mapped_dag = dag.copy_empty_like() for node in mapped_gates: - mapped_dag.apply_operation_back(op=node.op, qargs=node.qargs, cargs=node.cargs) + mapped_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) return mapped_dag diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 77a47e068b88..d7298f5370d6 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -364,7 +364,7 @@ def apply_swaps(dest_dag, swaps, layout): physical_qubits[layout.virtual_to_physical(b)], ) layout.swap_virtual(a, b) - dest_dag.apply_operation_back(SwapGate(), qubits, ()) + dest_dag.apply_operation_back(SwapGate(), qubits, (), check=False) def recurse(dest_dag, source_dag, result, root_logical_map, layout): """The main recursive worker. Mutates ``dest_dag`` and ``layout`` and returns them. @@ -384,6 +384,7 @@ def recurse(dest_dag, source_dag, result, root_logical_map, layout): for q in node.qargs ], node.cargs, + check=False, ) continue @@ -419,7 +420,7 @@ def recurse(dest_dag, source_dag, result, root_logical_map, layout): # Apply the control flow gate to the dag. mapped_node = node.op.replace_blocks(mapped_blocks) mapped_node_qargs = mapped_blocks[0].qubits if mapped_blocks else () - dest_dag.apply_operation_back(mapped_node, mapped_node_qargs, node.cargs) + dest_dag.apply_operation_back(mapped_node, mapped_node_qargs, node.cargs, check=False) return dest_dag, layout root_logical_map = {bit: index for index, bit in enumerate(in_dag.qubits)} diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 81304b4b4e4b..90001bb06cc8 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -240,7 +240,7 @@ def _layer_permutation(self, dag, layer_partition, layout, qubit_subset, couplin for idx in range(len(edges) // 2): swap_src = self._int_to_qubit[edges[2 * idx]] swap_tgt = self._int_to_qubit[edges[2 * idx + 1]] - trial_circuit.apply_operation_back(SwapGate(), [swap_src, swap_tgt], []) + trial_circuit.apply_operation_back(SwapGate(), (swap_src, swap_tgt), (), check=False) best_circuit = trial_circuit # Otherwise, we return our result for this layer @@ -445,7 +445,7 @@ def _controlflow_layer_update(self, dagcircuit_output, layer_dag, current_layout new_op = node.op.replace_blocks(block_circuits) new_qargs = block_circuits[0].qubits - dagcircuit_output.apply_operation_back(new_op, new_qargs, node.cargs) + dagcircuit_output.apply_operation_back(new_op, new_qargs, node.cargs, check=False) return final_layout def _new_seed(self): @@ -502,7 +502,10 @@ def _dag_from_block(block, node, root_dag): # mapping when required, so we use that with a dummy block. out.add_clbits(node.cargs) dummy = out.apply_operation_back( - Instruction("dummy", len(node.qargs), len(node.cargs), []), node.qargs, node.cargs + Instruction("dummy", len(node.qargs), len(node.cargs), []), + node.qargs, + node.cargs, + check=False, ) wire_map = dict(itertools.chain(zip(block.qubits, node.qargs), zip(block.clbits, node.cargs))) out.substitute_node_with_dag(dummy, circuit_to_dag(block), wires=wire_map) diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 063b94611bdc..9ee0f4988b4a 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -131,10 +131,10 @@ def run(self, dag): for bit in node.qargs: delta = t0 - idle_before[bit] if delta > 0 and self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) idle_before[bit] = t1 - new_dag.apply_operation_front(node.op, node.qargs, node.cargs) + new_dag.apply_operation_front(node.op, node.qargs, node.cargs, check=False) circuit_duration = max(idle_before.values()) for bit, before in idle_before.items(): @@ -142,7 +142,7 @@ def run(self, dag): if not (delta > 0 and isinstance(bit, Qubit)): continue if self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) new_dag.name = dag.name new_dag.metadata = dag.metadata diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py index 164e9557fbb7..668d65f6abd5 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -156,7 +156,7 @@ def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: for q in qubits: if qubit_stop_times[q] < until: idle_duration = until - qubit_stop_times[q] - new_dag.apply_operation_back(Delay(idle_duration, unit), [q]) + new_dag.apply_operation_back(Delay(idle_duration, unit), (q,), check=False) for node in dag.topological_op_nodes(): # choose appropriate clbit available time depending on op @@ -181,7 +181,7 @@ def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: if not isinstance(node.op, Delay): # exclude delays for combining consecutive delays pad_with_delays(node.qargs, until=start_time, unit=time_unit) - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) stop_time = start_time + node.op.duration # update time table diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 5b17ca52d190..7e5245b2e3fc 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -201,26 +201,26 @@ def run(self, dag): for nd in dag.topological_op_nodes(): if not isinstance(nd.op, Delay): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) continue dag_qubit = nd.qargs[0] physical_qubit = dag.find_bit(dag_qubit).index if physical_qubit not in self._qubits: # skip unwanted qubits - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) continue pred = next(dag.predecessors(nd)) succ = next(dag.successors(nd)) if self._skip_reset_qubits: # discount initial delays if isinstance(pred, DAGInNode) or isinstance(pred.op, Reset): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) continue dd_sequence_duration = index_sequence_duration_map[physical_qubit] slack = nd.op.duration - dd_sequence_duration if slack <= 0: # dd doesn't fit - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) continue if num_pulses == 1: # special case of using a single gate for DD @@ -242,7 +242,7 @@ def run(self, dag): sequence_gphase += phase # don't do anything if there's no single-qubit gate to absorb the inverse else: - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) + new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) continue # insert the actual DD sequence @@ -253,9 +253,9 @@ def run(self, dag): for tau, gate in itertools.zip_longest(taus, self._dd_sequence): if tau > 0: - new_dag.apply_operation_back(Delay(tau), [dag_qubit]) + new_dag.apply_operation_back(Delay(tau), [dag_qubit], check=False) if gate is not None: - new_dag.apply_operation_back(gate, [dag_qubit]) + new_dag.apply_operation_back(gate, [dag_qubit], check=False) new_dag.global_phase = _mod_2pi(new_dag.global_phase + sequence_gphase) diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 0c5078eb22a4..0bac3ba95e96 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -216,7 +216,7 @@ def _apply_scheduled_op( if isinstance(clbits, Clbit): clbits = [clbits] - new_node = dag.apply_operation_back(oper, qargs=qubits, cargs=clbits) + new_node = dag.apply_operation_back(oper, qargs=qubits, cargs=clbits, check=False) self.property_set["node_start_time"][new_node] = t_start def _pad( diff --git a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py index e510ddc87aeb..2222ef064010 100644 --- a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py +++ b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py @@ -63,7 +63,9 @@ def run(self, dag): # from an unmeasured qubit after a measure. final_qubits = dag.qubits - barrier_layer.apply_operation_back(Barrier(len(final_qubits)), list(final_qubits), []) + barrier_layer.apply_operation_back( + Barrier(len(final_qubits)), final_qubits, (), check=False + ) # Preserve order of final ops collected earlier from the original DAG. ordered_final_nodes = [ @@ -72,7 +74,9 @@ def run(self, dag): # Move final ops to the new layer and append the new layer to the DAG. for final_node in ordered_final_nodes: - barrier_layer.apply_operation_back(final_node.op, final_node.qargs, final_node.cargs) + barrier_layer.apply_operation_back( + final_node.op, final_node.qargs, final_node.cargs, check=False + ) for final_op in final_ops: dag.remove_op_node(final_op) diff --git a/qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py b/qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py index c5ff85926979..bf00aea87d40 100644 --- a/qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py +++ b/qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py @@ -74,7 +74,10 @@ def _run_inner(self, dag): if isinstance(target, ClassicalRegister): replacement.add_creg(target) replacement.apply_operation_back( - IfElseOp((target, value), block_body), block_body.qubits, block_body.clbits + IfElseOp((target, value), block_body), + block_body.qubits, + block_body.clbits, + check=False, ) wire_map = {bit: bit for bit in block_body.qubits + block_body.clbits} dag.substitute_node_with_dag(node, replacement, wire_map, propagate_condition=False) diff --git a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py index f77870047c9d..d0e58f55ce97 100644 --- a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py +++ b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py @@ -79,11 +79,11 @@ def run(self, dag): qubits = node_to_barrier_qubits[node] # qubits are stored as a set, need to convert to a list new_dag.apply_operation_back( - Barrier(len(qubits)), qargs=sorted(qubits, key=indices.get) + Barrier(len(qubits)), qargs=sorted(qubits, key=indices.get), check=False ) else: # copy the condition over too - new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) return new_dag @staticmethod diff --git a/releasenotes/notes/dag-appenders-check-84d4ef20c1e20fd0.yaml b/releasenotes/notes/dag-appenders-check-84d4ef20c1e20fd0.yaml new file mode 100644 index 000000000000..4126b91bb869 --- /dev/null +++ b/releasenotes/notes/dag-appenders-check-84d4ef20c1e20fd0.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The :class:`.DAGCircuit` methods :meth:`~.DAGCircuit.apply_operation_back` and + :meth:`~.DAGCircuit.apply_operation_front` have gained a ``check`` keyword argument that can be + set ``False`` to skip validation that the inputs uphold the :class:`.DAGCircuit` data-structure + invariants. This is useful as a performance optimisation when the DAG is being built from + known-good data, such as during transpiler passes.