From ee768788d10ac22512e726245ebf9fd46c971d50 Mon Sep 17 00:00:00 2001 From: Haimeng Zhang Date: Mon, 18 Nov 2024 16:59:51 -0500 Subject: [PATCH 1/4] Fix an issue that `InverseCancellation` does not run in classical blocks (#13437) --- qiskit/transpiler/passes/optimization/inverse_cancellation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py index 40876679e8d9..508cfc66946a 100644 --- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py +++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py @@ -19,6 +19,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passes.utils import control_flow from qiskit._accelerate.inverse_cancellation import inverse_cancellation @@ -74,6 +75,7 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]): super().__init__() + @control_flow.trivial_recurse def run(self, dag: DAGCircuit): """Run the InverseCancellation pass on `dag`. From 95d29051624d9a1a9b2ee5a8d8f36c13cef97080 Mon Sep 17 00:00:00 2001 From: Haimeng Zhang Date: Thu, 21 Nov 2024 18:35:53 -0500 Subject: [PATCH 2/4] Add test functions for pass running in classical blocks. --- .../transpiler/test_inverse_cancellation.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/python/transpiler/test_inverse_cancellation.py b/test/python/transpiler/test_inverse_cancellation.py index 64d7812888b3..75dd538de814 100644 --- a/test/python/transpiler/test_inverse_cancellation.py +++ b/test/python/transpiler/test_inverse_cancellation.py @@ -21,6 +21,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import InverseCancellation from qiskit.transpiler import PassManager +from qiskit.circuit import Clbit, Qubit from qiskit.circuit.library import ( RXGate, HGate, @@ -398,6 +399,58 @@ def test_backwards_pair(self): new_circ = inverse_pass(qc) self.assertEqual(new_circ, QuantumCircuit(1)) + def test_if_else(self): + """Test that the pass recurses in a simple if-else.""" + pass_ = InverseCancellation([CXGate()]) + + inner_test = QuantumCircuit(4, 1) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) + + inner_expected = QuantumCircuit(4, 1) + inner_expected.cx(2, 3) + + test = QuantumCircuit(4, 1) + test.h(0) + test.measure(0, 0) + test.if_else((0, True), inner_test.copy(), inner_test.copy(), range(4), [0]) + + expected = QuantumCircuit(4, 1) + expected.h(0) + expected.measure(0, 0) + expected.if_else((0, True), inner_expected, inner_expected, range(4), [0]) + + self.assertEqual(pass_(test), expected) + + def test_nested_control_flow(self): + """Test that collection recurses into nested control flow.""" + pass_ = InverseCancellation([CXGate()]) + qubits = [Qubit() for _ in [None] * 4] + clbit = Clbit() + + inner_test = QuantumCircuit(qubits, [clbit]) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) + + inner_expected = QuantumCircuit(qubits, [clbit]) + inner_expected.cx(2, 3) + + true_body = QuantumCircuit(qubits, [clbit]) + true_body.while_loop((clbit, True), inner_test.copy(), [0, 1, 2, 3], [0]) + + test = QuantumCircuit(qubits, [clbit]) + test.for_loop(range(2), None, inner_test.copy(), [0, 1, 2, 3], [0]) + test.if_else((clbit, True), true_body, None, [0, 1, 2, 3], [0]) + + expected_if_body = QuantumCircuit(qubits, [clbit]) + expected_if_body.while_loop((clbit, True), inner_expected, [0, 1, 2, 3], [0]) + expected = QuantumCircuit(qubits, [clbit]) + expected.for_loop(range(2), None, inner_expected, [0, 1, 2, 3], [0]) + expected.if_else((clbit, True), expected_if_body, None, [0, 1, 2, 3], [0]) + + self.assertEqual(pass_(test), expected) if __name__ == "__main__": unittest.main() From 6f1a46fb9e24ee45b398088838a9038a6f515717 Mon Sep 17 00:00:00 2001 From: Haimeng Zhang Date: Thu, 21 Nov 2024 21:10:00 -0500 Subject: [PATCH 3/4] Reformat test function file --- .../transpiler/test_inverse_cancellation.py | 89 ++++++++++--------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/test/python/transpiler/test_inverse_cancellation.py b/test/python/transpiler/test_inverse_cancellation.py index 75dd538de814..02072dc32c3c 100644 --- a/test/python/transpiler/test_inverse_cancellation.py +++ b/test/python/transpiler/test_inverse_cancellation.py @@ -399,58 +399,59 @@ def test_backwards_pair(self): new_circ = inverse_pass(qc) self.assertEqual(new_circ, QuantumCircuit(1)) - def test_if_else(self): - """Test that the pass recurses in a simple if-else.""" - pass_ = InverseCancellation([CXGate()]) + def test_if_else(self): + """Test that the pass recurses in a simple if-else.""" + pass_ = InverseCancellation([CXGate()]) - inner_test = QuantumCircuit(4, 1) - inner_test.cx(0, 1) - inner_test.cx(0, 1) - inner_test.cx(2, 3) + inner_test = QuantumCircuit(4, 1) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) - inner_expected = QuantumCircuit(4, 1) - inner_expected.cx(2, 3) + inner_expected = QuantumCircuit(4, 1) + inner_expected.cx(2, 3) - test = QuantumCircuit(4, 1) - test.h(0) - test.measure(0, 0) - test.if_else((0, True), inner_test.copy(), inner_test.copy(), range(4), [0]) + test = QuantumCircuit(4, 1) + test.h(0) + test.measure(0, 0) + test.if_else((0, True), inner_test.copy(), inner_test.copy(), range(4), [0]) - expected = QuantumCircuit(4, 1) - expected.h(0) - expected.measure(0, 0) - expected.if_else((0, True), inner_expected, inner_expected, range(4), [0]) + expected = QuantumCircuit(4, 1) + expected.h(0) + expected.measure(0, 0) + expected.if_else((0, True), inner_expected, inner_expected, range(4), [0]) - self.assertEqual(pass_(test), expected) + self.assertEqual(pass_(test), expected) - def test_nested_control_flow(self): - """Test that collection recurses into nested control flow.""" + def test_nested_control_flow(self): + """Test that collection recurses into nested control flow.""" pass_ = InverseCancellation([CXGate()]) - qubits = [Qubit() for _ in [None] * 4] - clbit = Clbit() - - inner_test = QuantumCircuit(qubits, [clbit]) - inner_test.cx(0, 1) - inner_test.cx(0, 1) - inner_test.cx(2, 3) - - inner_expected = QuantumCircuit(qubits, [clbit]) - inner_expected.cx(2, 3) - - true_body = QuantumCircuit(qubits, [clbit]) - true_body.while_loop((clbit, True), inner_test.copy(), [0, 1, 2, 3], [0]) - - test = QuantumCircuit(qubits, [clbit]) - test.for_loop(range(2), None, inner_test.copy(), [0, 1, 2, 3], [0]) - test.if_else((clbit, True), true_body, None, [0, 1, 2, 3], [0]) - - expected_if_body = QuantumCircuit(qubits, [clbit]) - expected_if_body.while_loop((clbit, True), inner_expected, [0, 1, 2, 3], [0]) - expected = QuantumCircuit(qubits, [clbit]) - expected.for_loop(range(2), None, inner_expected, [0, 1, 2, 3], [0]) - expected.if_else((clbit, True), expected_if_body, None, [0, 1, 2, 3], [0]) - + qubits = [Qubit() for _ in [None] * 4] + clbit = Clbit() + + inner_test = QuantumCircuit(qubits, [clbit]) + inner_test.cx(0, 1) + inner_test.cx(0, 1) + inner_test.cx(2, 3) + + inner_expected = QuantumCircuit(qubits, [clbit]) + inner_expected.cx(2, 3) + + true_body = QuantumCircuit(qubits, [clbit]) + true_body.while_loop((clbit, True), inner_test.copy(), [0, 1, 2, 3], [0]) + + test = QuantumCircuit(qubits, [clbit]) + test.for_loop(range(2), None, inner_test.copy(), [0, 1, 2, 3], [0]) + test.if_else((clbit, True), true_body, None, [0, 1, 2, 3], [0]) + + expected_if_body = QuantumCircuit(qubits, [clbit]) + expected_if_body.while_loop((clbit, True), inner_expected, [0, 1, 2, 3], [0]) + expected = QuantumCircuit(qubits, [clbit]) + expected.for_loop(range(2), None, inner_expected, [0, 1, 2, 3], [0]) + expected.if_else((clbit, True), expected_if_body, None, [0, 1, 2, 3], [0]) + self.assertEqual(pass_(test), expected) + if __name__ == "__main__": unittest.main() From dd4f1c5f4008a77d00e743fd0fc6d917aea3e89e Mon Sep 17 00:00:00 2001 From: Haimeng Zhang Date: Tue, 26 Nov 2024 15:56:54 -0500 Subject: [PATCH 4/4] Add a release note --- .../notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml diff --git a/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml b/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml new file mode 100644 index 000000000000..7d6999677b2b --- /dev/null +++ b/releasenotes/notes/fix-inverse-cancellation-c7f4debcde4a705a.yaml @@ -0,0 +1,3 @@ +fixes: + - | + The transpilation pass :class`.InverseCancellation` now runs inside of flow controlled blocks. Previously, it ignores the pairs of gates in classical blocks that can be cancelled. Refer to `#13437 ` for more details.