diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 35313de08ec5..1eba7e81f58d 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -30,7 +30,7 @@ import rustworkx as rx from qiskit.circuit import ControlFlowOp, ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp -from qiskit.circuit.controlflow import condition_resources +from qiskit.circuit.controlflow import condition_resources, node_resources from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.circuit.gate import Gate @@ -476,17 +476,26 @@ def _check_bits(self, args, amap): raise DAGCircuitError(f"(qu)bit {wire} not found in {amap}") @staticmethod - def _bits_in_condition(cond): - """Return a list of bits in the given condition. + def _bits_in_operation(operation): + """Return an iterable over the classical bits that are inherent to an instruction. This + includes a `condition`, or the `target` of a :class:`.ControlFlowOp`. Args: - cond (tuple or expr.Expr or None): optional condition in any form that the control-flow - operations accept. + instruction: the :class:`~.circuit.Instruction` instance for a node. Returns: - list[Clbit]: list of classical bits + Iterable[Clbit]: the :class:`.Clbit`\\ s involved. """ - return [] if cond is None else list(condition_resources(cond).clbits) + if (condition := getattr(operation, "condition", None)) is not None: + yield from condition_resources(condition).clbits + if isinstance(operation, SwitchCaseOp): + target = operation.target + if isinstance(target, Clbit): + yield target + elif isinstance(target, ClassicalRegister): + yield from target + else: + yield from node_resources(target).clbits def _increment_op(self, op): if op.name in self._op_names: @@ -571,8 +580,7 @@ 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 = self._bits_in_condition(getattr(op, "condition", None)) - all_cbits = set(all_cbits).union(cargs) + all_cbits = set(self._bits_in_operation(op)).union(cargs) self._check_condition(op.name, getattr(op, "condition", None)) self._check_bits(qargs, self.output_map) @@ -603,8 +611,7 @@ def apply_operation_front(self, op, qargs=(), cargs=()): Raises: DAGCircuitError: if initial nodes connected to multiple out edges """ - all_cbits = self._bits_in_condition(getattr(op, "condition", None)) - all_cbits.extend(cargs) + all_cbits = set(self._bits_in_operation(op)).union(cargs) self._check_condition(op.name, getattr(op, "condition", None)) self._check_bits(qargs, self.input_map) @@ -1183,9 +1190,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit # condition as well. if not propagate_condition: node_wire_order += [ - bit - for bit in self._bits_in_condition(getattr(node.op, "condition", None)) - if bit not in node_cargs + bit for bit in self._bits_in_operation(node.op) if bit not in node_cargs ] if len(wires) != len(node_wire_order): raise DAGCircuitError( @@ -1729,8 +1734,6 @@ def serial_layers(self): op = copy.copy(next_node.op) qargs = copy.copy(next_node.qargs) cargs = copy.copy(next_node.cargs) - condition = copy.copy(getattr(next_node.op, "condition", None)) - _ = self._bits_in_condition(condition) # Add node to new_layer new_layer.apply_operation_back(op, qargs, cargs) diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 2702786cde3a..0baeb9442779 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -311,7 +311,7 @@ def _inner_run(self, dag, coupling_map): for node in dag.topological_op_nodes(): cargs = {original_clbit_indices[x] for x in node.cargs} if node.op.condition is not None: - for clbit in dag._bits_in_condition(node.op.condition): + for clbit in dag._bits_in_operation(node.op): cargs.add(original_clbit_indices[clbit]) dag_list.append( diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index c17b7f09f975..def27c8d13a1 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -240,7 +240,7 @@ def run(self, dag): for node in dag.topological_op_nodes(): cargs = {self._clbit_indices[x] for x in node.cargs} if node.op.condition is not None: - for clbit in dag._bits_in_condition(node.op.condition): + for clbit in dag._bits_in_operation(node.op): cargs.add(self._clbit_indices[clbit]) dag_list.append( diff --git a/test/python/converters/test_circuit_to_dag.py b/test/python/converters/test_circuit_to_dag.py index f64ba153ccde..a4e9df0db9c7 100644 --- a/test/python/converters/test_circuit_to_dag.py +++ b/test/python/converters/test_circuit_to_dag.py @@ -14,8 +14,9 @@ import unittest +from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Clbit +from qiskit.circuit.classical import expr from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase @@ -51,6 +52,58 @@ def test_calibrations(self): circuit_out = dag_to_circuit(dag) self.assertEqual(len(circuit_out.calibrations), 1) + def test_wires_from_expr_nodes_condition(self): + """Test that the classical wires implied by an `Expr` node in a control-flow op's + `condition` are correctly transferred.""" + # The control-flow builder interface always includes any classical wires in the blocks of + # the operation, so we test by using manually constructed blocks that don't do that. It's + # not required by the `QuantumCircuit` model (just like `c_if` instructions don't expand + # their `cargs`). + inner = QuantumCircuit(1) + inner.x(0) + cr1 = ClassicalRegister(2, "a") + cr2 = ClassicalRegister(2, "b") + clbit = Clbit() + outer = QuantumCircuit(QuantumRegister(1), cr1, cr2, [clbit]) + # Note that 'cr2' is not in the condition. + outer.if_test(expr.logic_and(expr.equal(cr1, 3), expr.logic_not(clbit)), inner, [0], []) + outer.while_loop(expr.logic_or(expr.less(2, cr1), clbit), inner, [0], []) + + dag = circuit_to_dag(outer) + expected_wires = set(outer.qubits) | set(cr1) | {clbit} + for node in dag.topological_op_nodes(): + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.condition, expr.Expr) + self.assertEqual(test_wires, expected_wires) + + roundtripped = dag_to_circuit(dag) + for original, test in zip(outer, roundtripped): + self.assertEqual(original.operation.condition, test.operation.condition) + + def test_wires_from_expr_nodes_target(self): + """Test that the classical wires implied by an `Expr` node in a control-flow op's + `target` are correctly transferred.""" + case_1 = QuantumCircuit(1) + case_1.x(0) + case_2 = QuantumCircuit(1) + case_2.y(0) + cr1 = ClassicalRegister(2, "a") + cr2 = ClassicalRegister(2, "b") + outer = QuantumCircuit(QuantumRegister(1), cr1, cr2) + # Note that 'cr2' is not in the condition. + outer.switch(expr.bit_and(cr1, 2), [(1, case_1), (2, case_2)], [0], []) + + dag = circuit_to_dag(outer) + expected_wires = set(outer.qubits) | set(cr1) + for node in dag.topological_op_nodes(): + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.target, expr.Expr) + self.assertEqual(test_wires, expected_wires) + + roundtripped = dag_to_circuit(dag) + for original, test in zip(outer, roundtripped): + self.assertEqual(original.operation.target, test.operation.target) + if __name__ == "__main__": unittest.main(verbosity=2) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 0a61bb499ce7..3b6016d1791a 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -20,25 +20,26 @@ import rustworkx as rx from numpy import pi -from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGInNode, DAGOutNode -from qiskit.circuit import QuantumRegister -from qiskit.circuit import ClassicalRegister, Clbit -from qiskit.circuit import QuantumCircuit, Qubit -from qiskit.circuit import Measure -from qiskit.circuit import Reset -from qiskit.circuit import Delay -from qiskit.circuit import Gate, Instruction -from qiskit.circuit import Parameter -from qiskit.circuit.library.standard_gates.i import IGate -from qiskit.circuit.library.standard_gates.h import HGate -from qiskit.circuit.library.standard_gates.x import CXGate -from qiskit.circuit.library.standard_gates.z import CZGate -from qiskit.circuit.library.standard_gates.x import XGate -from qiskit.circuit.library.standard_gates.y import YGate -from qiskit.circuit.library.standard_gates.u1 import U1Gate -from qiskit.circuit.library.standard_gates.rx import RXGate -from qiskit.circuit.barrier import Barrier -from qiskit.dagcircuit.exceptions import DAGCircuitError +from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGInNode, DAGOutNode, DAGCircuitError +from qiskit.circuit import ( + QuantumCircuit, + QuantumRegister, + ClassicalRegister, + Clbit, + Qubit, + Measure, + Delay, + Reset, + Gate, + Instruction, + Parameter, + Barrier, + SwitchCaseOp, + IfElseOp, + WhileLoopOp, +) +from qiskit.circuit.classical import expr +from qiskit.circuit.library import IGate, HGate, CXGate, CZGate, XGate, YGate, U1Gate, RXGate from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -580,6 +581,68 @@ def test_apply_operation_front(self): self.assertIn(reset_node, set(self.dag.predecessors(h_node))) + def test_apply_operation_expr_condition(self): + """Test that the operation-applying functions correctly handle wires implied from `Expr` + nodes in the `condition` field of `ControlFlowOp` instances.""" + inner = QuantumCircuit(1) + inner.x(0) + + qr = QuantumRegister(1) + cr1 = ClassicalRegister(2, "a") + cr2 = ClassicalRegister(2, "b") + clbit = Clbit() + + dag = DAGCircuit() + dag.add_qreg(qr) + dag.add_creg(cr1) + dag.add_creg(cr2) + dag.add_clbits([clbit]) + + # Note that 'cr2' is not in either condition. + expected_wires = set(qr) | set(cr1) | {clbit} + + op = IfElseOp(expr.logic_and(expr.equal(cr1, 3), expr.logic_not(clbit)), inner, None) + node = dag.apply_operation_back(op, qr, ()) + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.condition, expr.Expr) + self.assertEqual(test_wires, expected_wires) + + op = WhileLoopOp(expr.logic_or(expr.less(2, cr1), clbit), inner) + node = dag.apply_operation_front(op, qr, ()) + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.condition, expr.Expr) + self.assertEqual(test_wires, expected_wires) + + def test_apply_operation_expr_target(self): + """Test that the operation-applying functions correctly handle wires implied from `Expr` + nodes in the `target` field of `SwitchCaseOp`.""" + case_1 = QuantumCircuit(1) + case_1.x(0) + case_2 = QuantumCircuit(1) + case_2.y(0) + qr = QuantumRegister(1) + cr1 = ClassicalRegister(2, "a") + cr2 = ClassicalRegister(2, "b") + # Note that 'cr2' is not in the condition. + op = SwitchCaseOp(expr.bit_and(cr1, 2), [(1, case_1), (2, case_2)]) + + expected_wires = set(qr) | set(cr1) + + dag = DAGCircuit() + dag.add_qreg(qr) + dag.add_creg(cr1) + dag.add_creg(cr2) + + node = dag.apply_operation_back(op, qr, ()) + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.target, expr.Expr) + self.assertEqual(test_wires, expected_wires) + + node = dag.apply_operation_front(op, qr, ()) + test_wires = {wire for _source, _dest, wire in dag.edges(node)} + self.assertIsInstance(node.op.target, expr.Expr) + self.assertEqual(test_wires, expected_wires) + class TestDagNodeSelection(QiskitTestCase): """Test methods that select certain dag nodes"""