Skip to content

Commit

Permalink
Fix .decompose on control flow (#13545)
Browse files Browse the repository at this point in the history
* decompose control flow ops

* add reno and test

* trailing print

* thou shall support Python 3.9

* Fix comments & reno

Co-authored-by: Elena Peña Tapia <[email protected]>

---------

Co-authored-by: Elena Peña Tapia <[email protected]>
  • Loading branch information
Cryoris and ElePT authored Dec 10, 2024
1 parent 841cb29 commit 4495c67
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 4 deletions.
28 changes: 24 additions & 4 deletions qiskit/transpiler/passes/basis/decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -58,20 +60,26 @@ 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():
# Check in self.gates_to_decompose if the operation should be decomposed
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:
Expand Down Expand Up @@ -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
11 changes: 11 additions & 0 deletions releasenotes/notes/decompose-controlflow-7a7e38d402aed260.yaml
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/qiskit/issues/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 <https://github.com/Qiskit/qiskit/issues/13544>`__.
77 changes: 77 additions & 0 deletions test/python/transpiler/test_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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())

0 comments on commit 4495c67

Please sign in to comment.