diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 1772cbd65544..4466dd492944 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -17,8 +17,10 @@ from collections.abc import Sequence from typing import Type from fnmatch import fnmatch +import warnings from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.passes.utils import control_flow from qiskit.dagcircuit.dagnode import DAGOpNode from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.converters.circuit_to_dag import circuit_to_dag @@ -58,7 +60,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: output dag where ``gate`` was expanded. """ # We might use HLS to synthesize objects that do not have a definition - hls = HighLevelSynthesis() if self.apply_synthesis else None + hls = HighLevelSynthesis(qubits_initially_zero=False) if self.apply_synthesis else None # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): @@ -66,12 +68,18 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if not self._should_decompose(node): continue - if getattr(node.op, "definition", None) is None: + if node.is_control_flow(): + decomposition = control_flow.map_blocks(self.run, node.op) + dag.substitute_node(node, decomposition, inplace=True) + + elif getattr(node.op, "definition", None) is None: # if we try to synthesize, turn the node into a DAGCircuit and run HLS if self.apply_synthesis: + # note that node_as_dag does not include the condition, which will + # be propagated in ``substitute_node_with_dag`` node_as_dag = _node_to_dag(node) synthesized = hls.run(node_as_dag) - dag.substitute_node_with_dag(node, synthesized) + dag.substitute_node_with_dag(node, synthesized, propagate_condition=True) # else: no definition and synthesis not enabled, so we do nothing else: @@ -123,9 +131,21 @@ def _should_decompose(self, node: DAGOpNode) -> bool: def _node_to_dag(node: DAGOpNode) -> DAGCircuit: + # Control flow is already handled separately, however that does not capture + # c_if, which we are treating here. We explicitly ignore the condition attribute, + # which will be handled by ``substitute_node_with_dag``, so we create a copy of the node + # and set the condition to None. Once ``c_if`` is removed for 2.0, this block can go, too. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + if getattr(node.op, "condition", None) is not None: + op = node.op.copy() + op.condition = None + node = DAGOpNode(op, node.qargs, node.cargs) + + # create new dag and apply the operation dag = DAGCircuit() dag.add_qubits(node.qargs) dag.add_clbits(node.cargs) - dag.apply_operation_back(node.op, node.qargs, node.cargs) + return dag diff --git a/releasenotes/notes/decompose-controlflow-7a7e38d402aed260.yaml b/releasenotes/notes/decompose-controlflow-7a7e38d402aed260.yaml new file mode 100644 index 000000000000..3ac215e29ea8 --- /dev/null +++ b/releasenotes/notes/decompose-controlflow-7a7e38d402aed260.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + Fixed a bug where calling :meth:`.QuantumCircuit.decompose` on an instruction + that had no definition inside a ``c_if`` block would raise an error. + Fixed `#13493 `__. + - | + Operations inside a control flow (e.g. :meth:`.QuantumCircuit.for_loop`) were not + correctly decomposed when calling :meth:`.QuantumCircuit.decompose`. This + behavior is now fixed and instructions are unrolled. + Fixed `#13544 `__. diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 64a9b97440ba..ea783f3ff304 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -19,6 +19,7 @@ from qiskit.converters import circuit_to_dag from qiskit.circuit.library import HGate, CCXGate, U2Gate from qiskit.quantum_info.operators import Operator, Clifford + from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -352,3 +353,79 @@ def test_specify_hls_object(self): expected.h(0) self.assertEqual(expected, decomposed) + + def test_cif(self): + """Test decomposition with c_if.""" + circuit = QuantumCircuit(1, 1) + with self.assertWarns(DeprecationWarning): + circuit.x(0).c_if(0, 0) + + ops = circuit.decompose().count_ops() + self.assertEqual(ops.get("u3", 0), 1) + + def test_cif_no_definition(self): + """Test decomposition with c_if when the gate has no definition. + + Regression test of #13493. + """ + circuit = QuantumCircuit(1, 1) + with self.assertWarns(DeprecationWarning): + circuit.u(1, 2, 3, 0).c_if(0, 0) + + ops = circuit.decompose().count_ops() + self.assertEqual(ops.get("u", 0), 1) + + def test_control_flow_if(self): + """Test decompose with control flow.""" + qr = QuantumRegister(2) + cr = ClassicalRegister(1) + qc = QuantumCircuit(qr, cr) + + qc.p(0.2, 0) + qc.measure(0, 0) + + with qc.if_test((cr[0], 0)) as else_: + qc.cry(0.5, 0, 1) + with else_: + qc.crz(0.5, 0, 1) + + expect = qc.copy_empty_like() + expect.u(0, 0, 0.2, 0) + expect.measure(0, 0) + + with expect.if_test((cr[0], 0)) as else_: + expect.ry(0.25, 1) + expect.cx(0, 1) + expect.ry(-0.25, 1) + expect.cx(0, 1) + with else_: + expect.rz(0.25, 1) + expect.cx(0, 1) + expect.rz(-0.25, 1) + expect.cx(0, 1) + + self.assertEqual(expect, qc.decompose()) + + def test_control_flow_for(self): + """Test decompose with control flow.""" + qr = QuantumRegister(2) + cr = ClassicalRegister(1) + qc = QuantumCircuit(qr, cr) + + qc.p(0.2, 0) + qc.measure(0, 0) + + with qc.for_loop(range(3)): + qc.cry(0.5, 0, 1) + + expect = qc.copy_empty_like() + expect.u(0, 0, 0.2, 0) + expect.measure(0, 0) + + with expect.for_loop(range(3)): + expect.ry(0.25, 1) + expect.cx(0, 1) + expect.ry(-0.25, 1) + expect.cx(0, 1) + + self.assertEqual(expect, qc.decompose())