Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conversion transpiler pass from c_if to IfElseOp #8764

Merged
merged 7 commits into from
Sep 29, 2022
Merged
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
FixedPoint
ContainsInstruction
GatesInBasis
ConvertConditionsToIfOps
"""

# layout selection (placement)
Expand Down Expand Up @@ -278,3 +279,4 @@
from .utils import RemoveBarriers
from .utils import ContainsInstruction
from .utils import GatesInBasis
from .utils import ConvertConditionsToIfOps
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .remove_barriers import RemoveBarriers
from .contains_instruction import ContainsInstruction
from .gates_basis import GatesInBasis
from .convert_conditions_to_if_ops import ConvertConditionsToIfOps

# Utility functions
from . import control_flow
86 changes: 86 additions & 0 deletions qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Replace conditional instructions with equivalent :class:`.IfElseOp` objects."""

from qiskit.converters import dag_to_circuit, circuit_to_dag
from qiskit.circuit import (
CircuitInstruction,
ClassicalRegister,
Clbit,
ControlFlowOp,
IfElseOp,
QuantumCircuit,
)
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler import TransformationPass


class ConvertConditionsToIfOps(TransformationPass):
"""Convert instructions whose ``condition`` attribute is set to a non-``None`` value into the
equivalent single-statement :class:`.IfElseBlock`.

This is a simple pass aimed at easing the conversion from the old style of using
:meth:`.InstructionSet.c_if` into the new style of using more complex conditional logic."""

def _run_inner(self, dag):
"""Run the pass on one :class:`.DAGCircuit`, mutating it. Returns ``True`` if the circuit
was modified and ``False`` if not."""
modified = False
for node in dag.op_nodes():
if isinstance(node.op, ControlFlowOp):
modified_blocks = False
new_dags = []
for block in node.op.blocks:
new_dag = circuit_to_dag(block)
modified_blocks |= self._run_inner(new_dag)
new_dags.append(new_dag)
if not modified_blocks:
continue
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
dag.substitute_node(
node,
node.op.replace_blocks(dag_to_circuit(block) for block in new_dags),
inplace=True,
)
elif getattr(node.op, "condition", None) is None:
continue
else:
target, value = node.op.condition
clbits = list(node.cargs)
condition_clbits = [target] if isinstance(target, Clbit) else list(target)
clbits_set = set(clbits)
clbits += [bit for bit in condition_clbits if bit not in clbits_set]
block_body = QuantumCircuit(list(node.qargs) + clbits)
if isinstance(target, ClassicalRegister):
block_body.add_register(target)
new_op = node.op.copy()
new_op.condition = None
block_body._append(CircuitInstruction(new_op, node.qargs, node.cargs))
# Despite only being a node-for-node replacement, control-flow ops contain the
# condition bits in their cargs, which requires slightly different handling in the
# DAGCircuit methods right now.
replacement = DAGCircuit()
replacement.add_qubits(block_body.qubits)
replacement.add_clbits(block_body.clbits)
if isinstance(target, ClassicalRegister):
replacement.add_creg(target)
replacement.apply_operation_back(
IfElseOp((target, value), block_body), block_body.qubits, block_body.clbits
)
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)
modified = True
return modified

def run(self, dag):
self._run_inner(dag)
return dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
A new transpiler pass, :class:`.ConvertConditionsToIfOps` was added, which
can be manually run to convert old-style :meth:`.Instruction.c_if`-conditioned
instructions into :class:`.IfElseOp` objects. This is to help ease the transition
from the old type to the new type for backends. For most users, there is no
need to add this to your pass managers, and it is not included in any preset
pass managers.
139 changes: 139 additions & 0 deletions test/python/transpiler/test_convert_conditions_to_if_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# pylint: disable=missing-class-docstring,missing-module-docstring

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister, Qubit, Clbit
from qiskit.test import QiskitTestCase
from qiskit.test._canonical import canonicalize_control_flow
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import ConvertConditionsToIfOps


class TestConvertConditionsToIfOps(QiskitTestCase):
def test_simple_loose_bits(self):
"""Test that basic conversions work when operating on loose classical bits."""
bits = [Qubit(), Qubit(), Clbit(), Clbit()]

base = QuantumCircuit(bits)
base.h(0)
base.x(0).c_if(0, 1)
base.z(1).c_if(1, 0)
base.measure(0, 0)
base.measure(1, 1)
base.h(0)
base.x(0).c_if(0, 1)
base.cx(0, 1).c_if(1, 0)

expected = QuantumCircuit(bits)
expected.h(0)
with expected.if_test((expected.clbits[0], True)):
expected.x(0)
with expected.if_test((expected.clbits[1], False)):
expected.z(1)
expected.measure(0, 0)
expected.measure(1, 1)
expected.h(0)
with expected.if_test((expected.clbits[0], True)):
expected.x(0)
with expected.if_test((expected.clbits[1], False)):
expected.cx(0, 1)
expected = canonicalize_control_flow(expected)

output = PassManager([ConvertConditionsToIfOps()]).run(base)
self.assertEqual(output, expected)

def test_simple_registers(self):
"""Test that basic conversions work when operating on conditions over registers."""
registers = [QuantumRegister(2), ClassicalRegister(2), ClassicalRegister(1)]

base = QuantumCircuit(*registers)
base.h(0)
base.x(0).c_if(base.cregs[0], 1)
base.z(1).c_if(base.cregs[1], 0)
base.measure(0, 0)
base.measure(1, 2)
base.h(0)
base.x(0).c_if(base.cregs[0], 1)
base.cx(0, 1).c_if(base.cregs[1], 0)

expected = QuantumCircuit(*registers)
expected.h(0)
with expected.if_test((expected.cregs[0], 1)):
expected.x(0)
with expected.if_test((expected.cregs[1], 0)):
expected.z(1)
expected.measure(0, 0)
expected.measure(1, 2)
expected.h(0)
with expected.if_test((expected.cregs[0], 1)):
expected.x(0)
with expected.if_test((expected.cregs[1], 0)):
expected.cx(0, 1)
expected = canonicalize_control_flow(expected)

output = PassManager([ConvertConditionsToIfOps()]).run(base)
self.assertEqual(output, expected)

def test_nested_control_flow(self):
"""Test that the pass successfully converts instructions nested within control-flow
blocks."""
bits = [Clbit()]
registers = [QuantumRegister(3), ClassicalRegister(2)]

base = QuantumCircuit(*registers, bits)
base.x(0).c_if(bits[0], False)
with base.if_test((base.cregs[0], 0)) as else_:
base.z(1).c_if(bits[0], False)
with else_:
base.z(1).c_if(base.cregs[0], 1)
with base.for_loop(range(2)):
with base.while_loop((base.cregs[0], 1)):
base.cx(1, 2).c_if(base.cregs[0], 1)
base = canonicalize_control_flow(base)

expected = QuantumCircuit(*registers, bits)
with expected.if_test((bits[0], False)):
expected.x(0)
with expected.if_test((expected.cregs[0], 0)) as else_:
with expected.if_test((bits[0], False)):
expected.z(1)
with else_:
with expected.if_test((expected.cregs[0], 1)):
expected.z(1)
with expected.for_loop(range(2)):
with expected.while_loop((expected.cregs[0], 1)):
with expected.if_test((expected.cregs[0], 1)):
expected.cx(1, 2)
expected = canonicalize_control_flow(expected)

output = PassManager([ConvertConditionsToIfOps()]).run(base)
self.assertEqual(output, expected)

def test_no_op(self):
"""Test that the pass works when recursing into control-flow structures, but there's nothing
that actually needs replacing."""
bits = [Clbit()]
registers = [QuantumRegister(3), ClassicalRegister(2)]

base = QuantumCircuit(*registers, bits)
base.x(0)
with base.if_test((base.cregs[0], 0)) as else_:
base.z(1)
with else_:
base.z(2)
with base.for_loop(range(2)):
with base.while_loop((base.cregs[0], 1)):
base.cx(1, 2)
base = canonicalize_control_flow(base)
output = PassManager([ConvertConditionsToIfOps()]).run(base)
self.assertEqual(output, base)