-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
2b7282d
commit fca3864
Showing
5 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
qiskit/transpiler/passes/utils/convert_conditions_to_if_ops.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
9 changes: 9 additions & 0 deletions
9
releasenotes/notes/c_if-to-if_else-converter-2d48046de31814a8.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
139
test/python/transpiler/test_convert_conditions_to_if_ops.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |