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 control flow to CommutativeCancellation pass #9143

Merged
merged 20 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
760fbfc
add initial tests
ewinston Nov 13, 2022
aeb571e
tests passing
ewinston Nov 16, 2022
dfb8d31
Merge branch 'main' into controlflow/commutative_cancellation
ewinston Feb 8, 2023
450e678
remove debug code
ewinston Apr 19, 2023
935d787
Merge branch 'main' into controlflow/commutative_cancellation
ewinston Apr 19, 2023
ad93783
fix test for clbits
ewinston Apr 20, 2023
292b0d7
formatting
ewinston Apr 20, 2023
02036f0
Merge branch 'main' into controlflow/commutative_cancellation
jlapeyre Jun 22, 2023
6f30dcf
Merge branch 'main' into controlflow/commutative_cancellation
jlapeyre Jun 23, 2023
893c2c1
Lift creation of PassManager out of loop
jlapeyre Jun 23, 2023
68a8fce
Test commutative cancellation does not cross block boundaries
jlapeyre Jun 23, 2023
b6cdab4
Add release note for commutative cancellation in control flow blocks
jlapeyre Jun 23, 2023
0616274
Include CommutativeAnalysis pass explicitly when doing control flow b…
jlapeyre Jun 28, 2023
f8c2aaf
Use existing CommutativeCancellation pass in control flow op blocks
jlapeyre Jul 12, 2023
6cce375
Move import of PassManager from method to top of file
jlapeyre Jul 12, 2023
cdb6d60
Revert commit f8c2aaf because I missed a fix already to go
jlapeyre Jul 12, 2023
074fb21
Update qiskit/transpiler/passes/optimization/commutative_cancellation.py
jlapeyre Jul 12, 2023
433ac38
Merge branch 'main' into controlflow/commutative_cancellation
jlapeyre Jul 12, 2023
eeab1c6
Merge branch 'main' into controlflow/commutative_cancellation
mtreinish Jul 13, 2023
3906813
Merge branch 'main' into controlflow/commutative_cancellation
jlapeyre Jul 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions qiskit/circuit/commutation_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import numpy as np

from qiskit.circuit.operation import Operation
from qiskit.circuit.controlflow import ControlFlowOp
from qiskit.quantum_info.operators import Operator


Expand Down Expand Up @@ -88,6 +89,11 @@ def commute(
):
return False

# Commutation of ControlFlow gates also not supported yet. This may be
# pending a control flow graph.
if isinstance(op1, ControlFlowOp) or isinstance(op2, ControlFlowOp):
return False

# These lines are adapted from dag_dependency and say that two gates over
# different quantum and classical bits necessarily commute. This is more
# permissive that the check from commutation_analysis, as for example it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passmanager import PassManager
from qiskit.transpiler.passes.optimization.commutation_analysis import CommutationAnalysis
from qiskit.dagcircuit import DAGCircuit, DAGInNode, DAGOutNode
from qiskit.circuit.library.standard_gates.u1 import U1Gate
from qiskit.circuit.library.standard_gates.rx import RXGate
from qiskit.circuit.library.standard_gates.p import PhaseGate
from qiskit.circuit.library.standard_gates.rz import RZGate
from qiskit.circuit import ControlFlowOp


_CUTOFF_PRECISION = 1e-5
Expand Down Expand Up @@ -97,7 +99,6 @@ def run(self, dag):
# - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id,
# sec_commutation_set_id), the value is the list gates that share the same gate type,
# qubits and commutation sets.

for wire in dag.wires:
wire_commutation_set = self.property_set["commutation_set"][wire]

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

dag = self._handle_control_flow_ops(dag)

return dag

def _handle_control_flow_ops(self, dag):
"""
This is similar to transpiler/passes/utils/control_flow.py except that the
commutation analysis is redone for the control flow blocks.
"""

pass_manager = PassManager([CommutationAnalysis(), self])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating asking to move this to an instance scope, something like: self.control_flow_pm because CommutationAnalysis maintains a cache of commutation relationships which might be useful between multiple runs for >1 control flow block. But my hesitation is around whether sharing the cache between the blocks is sound or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be safe to reuse the cache between different blocks or even runs, since all it stores are gate identities like "CX(0, 1) commutes with Z(0)"; "CX(0, 1) does not commute with S(0)", etc.. I don't know if sharing this information would lead to a performance improvement, I guess it depends how many blocks there are and how similar these are in terms of gates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move forward here, we can always add this in a quick follow up without too much concern then

for node in dag.op_nodes(ControlFlowOp):
mapped_blocks = []
for block in node.op.blocks:
new_circ = pass_manager.run(block)
mapped_blocks.append(new_circ)
node.op = node.op.replace_blocks(mapped_blocks)
return dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
Enabled performing the :class:`qiskit.transpiler.passes.CommutativeCancellation` pass inside the
blocks of :class:`qiskit.circuit.ControlFlowOp`. This pass reorders some commuting gates and
reduces resulting pairs of self-inverse gates. Previously, the blocks in control flow operations
were skipped by this pass. The new feature operates recursively, that is, it will act on control
flow operations inside blocks.
108 changes: 108 additions & 0 deletions test/python/transpiler/test_commutative_cancellation.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,114 @@ def test_basic_classical_wires(self):
transpiled = PassManager([CommutativeCancellation()]).run(original)
self.assertEqual(original, transpiled)

def test_simple_if_else(self):
"""Test that the pass is not confused by if-else."""
base_test1 = QuantumCircuit(3, 3)
base_test1.x(1)
base_test1.cx(0, 1)
base_test1.x(1)

base_test2 = QuantumCircuit(3, 3)
base_test2.rz(0.1, 1)
base_test2.rz(0.1, 1)

test = QuantumCircuit(3, 3)
test.h(0)
test.x(0)
test.rx(0.2, 0)
test.measure(0, 0)
test.x(0)
test.if_else(
(test.clbits[0], True), base_test1.copy(), base_test2.copy(), test.qubits, test.clbits
)

expected = QuantumCircuit(3, 3)
expected.h(0)
expected.rx(np.pi + 0.2, 0)
expected.measure(0, 0)
expected.x(0)

expected_test1 = QuantumCircuit(3, 3)
expected_test1.cx(0, 1)

expected_test2 = QuantumCircuit(3, 3)
expected_test2.rz(0.2, 1)

expected.if_else(
(expected.clbits[0], True),
expected_test1.copy(),
expected_test2.copy(),
expected.qubits,
expected.clbits,
)

passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
new_circuit = passmanager.run(test)
self.assertEqual(new_circuit, expected)

def test_nested_control_flow(self):
"""Test that the pass does not add barrier into nested control flow."""
level2_test = QuantumCircuit(2, 1)
level2_test.cz(0, 1)
level2_test.cz(0, 1)
level2_test.cz(0, 1)
level2_test.measure(0, 0)

level1_test = QuantumCircuit(2, 1)
level1_test.for_loop((0,), None, level2_test.copy(), level1_test.qubits, level1_test.clbits)
level1_test.h(0)
level1_test.h(0)
level1_test.measure(0, 0)

test = QuantumCircuit(2, 1)
test.while_loop((test.clbits[0], True), level1_test.copy(), test.qubits, test.clbits)
test.measure(0, 0)

level2_expected = QuantumCircuit(2, 1)
level2_expected.cz(0, 1)
level2_expected.measure(0, 0)

level1_expected = QuantumCircuit(2, 1)
level1_expected.for_loop(
(0,), None, level2_expected.copy(), level1_expected.qubits, level1_expected.clbits
)
level1_expected.measure(0, 0)

expected = QuantumCircuit(2, 1)
expected.while_loop(
(expected.clbits[0], True), level1_expected.copy(), expected.qubits, expected.clbits
)
expected.measure(0, 0)

passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
new_circuit = passmanager.run(test)
self.assertEqual(new_circuit, expected)

def test_cancellation_not_crossing_block_boundary(self):
"""Test that the pass does cancel gates across control flow op block boundaries."""
test1 = QuantumCircuit(2, 2)
test1.x(1)
with test1.if_test((0, False)):
test1.cx(0, 1)
test1.x(1)

passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
new_circuit = passmanager.run(test1)
self.assertEqual(new_circuit, test1)

def test_cancellation_not_crossing_between_blocks(self):
"""Test that the pass does cancel gates in different control flow ops."""
test2 = QuantumCircuit(2, 2)
with test2.if_test((0, True)):
test2.x(1)
with test2.if_test((0, True)):
test2.cx(0, 1)
test2.x(1)

passmanager = PassManager([CommutationAnalysis(), CommutativeCancellation()])
new_circuit = passmanager.run(test2)
self.assertEqual(new_circuit, test2)


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