Skip to content

Commit

Permalink
Add conversion transpiler pass from c_if to IfElseOp (#8764)
Browse files Browse the repository at this point in the history
* Add conversion transpiler pass from c_if to IfElseOp

This is intended to be used to ease the transition from the old-style
conditions to the new-style form.  Backends may use it in their custom
pipelines, so they can guarantee they only need to deal with one form.

A further classical optimisation may be to group `IfElseOp` blocks that
share the same condition into a single operation, but this would likely
be a separate pass, if added, or potentially be the domain of a separate
part of the circuit-description/compilation/running pipeline.

* Remove nondeterministic bit ordering

* Add test of no-op behaviour

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
jakelishman and mergify[bot] authored Sep 29, 2022
1 parent 2b7282d commit fca3864
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
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
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)

0 comments on commit fca3864

Please sign in to comment.