Skip to content

Commit 733f78c

Browse files
ewinstonjlapeyremtreinish
authored
add control flow to CommutativeCancellation pass (Qiskit#9143)
* add initial tests * tests passing * remove debug code * fix test for clbits * formatting * Lift creation of PassManager out of loop In _handle_control_flow_ops, a new PassManager was created for each ControlFlowOp found. This commit moves the creation out of the loop over nodes. This should be a bit more efficient. I also considered mapped_blocks.clear() rather than reallocating. But in some simpler tests of clearing rather than reallocating, this is actually less performant. I think reallocating renders the code slightly more understandable. * Test commutative cancellation does not cross block boundaries These were requested in a review comment. The previous commit was also requested in a review comment. * Add release note for commutative cancellation in control flow blocks * Include CommutativeAnalysis pass explicitly when doing control flow blocks A new PassManager for CommutativeCancellation is constructed when descending into control flow blocks. This commit explicitly includes a CommutativeAnalysis pass in this construction rather than relying on `requires`. This change, while not necessary, is made for consistency with other explicit constructions of PassManagers containing these passes. * Use existing CommutativeCancellation pass in control flow op blocks A previous commit made a copy of the pass being used in the parent context to be used in the blocks of a control flow op. With this commit, we reuse the existing pass, as it is immutable. * Move import of PassManager from method to top of file * Revert commit f8c2aaf because I missed a fix already to go * Update qiskit/transpiler/passes/optimization/commutative_cancellation.py Co-authored-by: Matthew Treinish <[email protected]> --------- Co-authored-by: John Lapeyre <[email protected]> Co-authored-by: Matthew Treinish <[email protected]>
1 parent d62f780 commit 733f78c

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

qiskit/circuit/commutation_checker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import numpy as np
1818

1919
from qiskit.circuit.operation import Operation
20+
from qiskit.circuit.controlflow import ControlFlowOp
2021
from qiskit.quantum_info.operators import Operator
2122

2223

@@ -88,6 +89,11 @@ def commute(
8889
):
8990
return False
9091

92+
# Commutation of ControlFlow gates also not supported yet. This may be
93+
# pending a control flow graph.
94+
if isinstance(op1, ControlFlowOp) or isinstance(op2, ControlFlowOp):
95+
return False
96+
9197
# These lines are adapted from dag_dependency and say that two gates over
9298
# different quantum and classical bits necessarily commute. This is more
9399
# permissive that the check from commutation_analysis, as for example it

qiskit/transpiler/passes/optimization/commutative_cancellation.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
from qiskit.circuit.quantumregister import QuantumRegister
1919
from qiskit.transpiler.exceptions import TranspilerError
2020
from qiskit.transpiler.basepasses import TransformationPass
21+
from qiskit.transpiler.passmanager import PassManager
2122
from qiskit.transpiler.passes.optimization.commutation_analysis import CommutationAnalysis
2223
from qiskit.dagcircuit import DAGCircuit, DAGInNode, DAGOutNode
2324
from qiskit.circuit.library.standard_gates.u1 import U1Gate
2425
from qiskit.circuit.library.standard_gates.rx import RXGate
2526
from qiskit.circuit.library.standard_gates.p import PhaseGate
2627
from qiskit.circuit.library.standard_gates.rz import RZGate
28+
from qiskit.circuit import ControlFlowOp
2729

2830

2931
_CUTOFF_PRECISION = 1e-5
@@ -97,7 +99,6 @@ def run(self, dag):
9799
# - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id,
98100
# sec_commutation_set_id), the value is the list gates that share the same gate type,
99101
# qubits and commutation sets.
100-
101102
for wire in dag.wires:
102103
wire_commutation_set = self.property_set["commutation_set"][wire]
103104

@@ -186,4 +187,21 @@ def run(self, dag):
186187
if np.mod(total_angle, (2 * np.pi)) < _CUTOFF_PRECISION:
187188
dag.remove_op_node(run[0])
188189

190+
dag = self._handle_control_flow_ops(dag)
191+
192+
return dag
193+
194+
def _handle_control_flow_ops(self, dag):
195+
"""
196+
This is similar to transpiler/passes/utils/control_flow.py except that the
197+
commutation analysis is redone for the control flow blocks.
198+
"""
199+
200+
pass_manager = PassManager([CommutationAnalysis(), self])
201+
for node in dag.op_nodes(ControlFlowOp):
202+
mapped_blocks = []
203+
for block in node.op.blocks:
204+
new_circ = pass_manager.run(block)
205+
mapped_blocks.append(new_circ)
206+
node.op = node.op.replace_blocks(mapped_blocks)
189207
return dag
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
Enabled performing the :class:`qiskit.transpiler.passes.CommutativeCancellation` pass inside the
5+
blocks of :class:`qiskit.circuit.ControlFlowOp`. This pass reorders some commuting gates and
6+
reduces resulting pairs of self-inverse gates. Previously, the blocks in control flow operations
7+
were skipped by this pass. The new feature operates recursively, that is, it will act on control
8+
flow operations inside blocks.

test/python/transpiler/test_commutative_cancellation.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,114 @@ def test_basic_classical_wires(self):
654654
transpiled = PassManager([CommutativeCancellation()]).run(original)
655655
self.assertEqual(original, transpiled)
656656

657+
def test_simple_if_else(self):
658+
"""Test that the pass is not confused by if-else."""
659+
base_test1 = QuantumCircuit(3, 3)
660+
base_test1.x(1)
661+
base_test1.cx(0, 1)
662+
base_test1.x(1)
663+
664+
base_test2 = QuantumCircuit(3, 3)
665+
base_test2.rz(0.1, 1)
666+
base_test2.rz(0.1, 1)
667+
668+
test = QuantumCircuit(3, 3)
669+
test.h(0)
670+
test.x(0)
671+
test.rx(0.2, 0)
672+
test.measure(0, 0)
673+
test.x(0)
674+
test.if_else(
675+
(test.clbits[0], True), base_test1.copy(), base_test2.copy(), test.qubits, test.clbits
676+
)
677+
678+
expected = QuantumCircuit(3, 3)
679+
expected.h(0)
680+
expected.rx(np.pi + 0.2, 0)
681+
expected.measure(0, 0)
682+
expected.x(0)
683+
684+
expected_test1 = QuantumCircuit(3, 3)
685+
expected_test1.cx(0, 1)
686+
687+
expected_test2 = QuantumCircuit(3, 3)
688+
expected_test2.rz(0.2, 1)
689+
690+
expected.if_else(
691+
(expected.clbits[0], True),
692+
expected_test1.copy(),
693+
expected_test2.copy(),
694+
expected.qubits,
695+
expected.clbits,
696+
)
697+
698+
passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
699+
new_circuit = passmanager.run(test)
700+
self.assertEqual(new_circuit, expected)
701+
702+
def test_nested_control_flow(self):
703+
"""Test that the pass does not add barrier into nested control flow."""
704+
level2_test = QuantumCircuit(2, 1)
705+
level2_test.cz(0, 1)
706+
level2_test.cz(0, 1)
707+
level2_test.cz(0, 1)
708+
level2_test.measure(0, 0)
709+
710+
level1_test = QuantumCircuit(2, 1)
711+
level1_test.for_loop((0,), None, level2_test.copy(), level1_test.qubits, level1_test.clbits)
712+
level1_test.h(0)
713+
level1_test.h(0)
714+
level1_test.measure(0, 0)
715+
716+
test = QuantumCircuit(2, 1)
717+
test.while_loop((test.clbits[0], True), level1_test.copy(), test.qubits, test.clbits)
718+
test.measure(0, 0)
719+
720+
level2_expected = QuantumCircuit(2, 1)
721+
level2_expected.cz(0, 1)
722+
level2_expected.measure(0, 0)
723+
724+
level1_expected = QuantumCircuit(2, 1)
725+
level1_expected.for_loop(
726+
(0,), None, level2_expected.copy(), level1_expected.qubits, level1_expected.clbits
727+
)
728+
level1_expected.measure(0, 0)
729+
730+
expected = QuantumCircuit(2, 1)
731+
expected.while_loop(
732+
(expected.clbits[0], True), level1_expected.copy(), expected.qubits, expected.clbits
733+
)
734+
expected.measure(0, 0)
735+
736+
passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
737+
new_circuit = passmanager.run(test)
738+
self.assertEqual(new_circuit, expected)
739+
740+
def test_cancellation_not_crossing_block_boundary(self):
741+
"""Test that the pass does cancel gates across control flow op block boundaries."""
742+
test1 = QuantumCircuit(2, 2)
743+
test1.x(1)
744+
with test1.if_test((0, False)):
745+
test1.cx(0, 1)
746+
test1.x(1)
747+
748+
passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
749+
new_circuit = passmanager.run(test1)
750+
self.assertEqual(new_circuit, test1)
751+
752+
def test_cancellation_not_crossing_between_blocks(self):
753+
"""Test that the pass does cancel gates in different control flow ops."""
754+
test2 = QuantumCircuit(2, 2)
755+
with test2.if_test((0, True)):
756+
test2.x(1)
757+
with test2.if_test((0, True)):
758+
test2.cx(0, 1)
759+
test2.x(1)
760+
761+
passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
762+
new_circuit = passmanager.run(test2)
763+
self.assertEqual(new_circuit, test2)
764+
657765

658766
if __name__ == "__main__":
659767
unittest.main()

0 commit comments

Comments
 (0)