From ed88e0b1a5fe2deb7111bdfb6d0245447e0768d4 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 24 Feb 2021 18:03:39 +0100 Subject: [PATCH 1/4] swap_cancellation pass --- qiskit/transpiler/passes/__init__.py | 2 + .../passes/optimization/__init__.py | 1 + .../passes/optimization/swap_cancellation.py | 54 +++++++++++++++++++ .../transpiler/test_swap_cancellation.py | 45 ++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 qiskit/transpiler/passes/optimization/swap_cancellation.py create mode 100644 test/python/transpiler/test_swap_cancellation.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 152ce63ac130..12ac7f3471e0 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -68,6 +68,7 @@ Collect2qBlocks ConsolidateBlocks CXCancellation + SWAPCancellation CommutationAnalysis CommutativeCancellation RemoveDiagonalGatesBeforeMeasure @@ -156,6 +157,7 @@ from .optimization import CommutationAnalysis from .optimization import CommutativeCancellation from .optimization import CXCancellation +from .optimization import SWAPCancellation from .optimization import OptimizeSwapBeforeMeasure from .optimization import RemoveResetInZeroState from .optimization import RemoveDiagonalGatesBeforeMeasure diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 8a418aa0df6f..a5f8a327c660 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -19,6 +19,7 @@ from .commutation_analysis import CommutationAnalysis from .commutative_cancellation import CommutativeCancellation from .cx_cancellation import CXCancellation +from .swap_cancellation import SWAPCancellation from .optimize_swap_before_measure import OptimizeSwapBeforeMeasure from .remove_reset_in_zero_state import RemoveResetInZeroState from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure diff --git a/qiskit/transpiler/passes/optimization/swap_cancellation.py b/qiskit/transpiler/passes/optimization/swap_cancellation.py new file mode 100644 index 000000000000..98acea5c2ca1 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/swap_cancellation.py @@ -0,0 +1,54 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +"""Cancel back-to-back `swap` gates in dag.""" + +from qiskit.transpiler.basepasses import TransformationPass + + +class SWAPCancellation(TransformationPass): + """Cancel back-to-back `swap` gates in dag.""" + + def run(self, dag): + """Run the CXCancellation pass on `dag`. + + Args: + dag (DAGCircuit): the directed acyclic graph to run on. + + Returns: + DAGCircuit: Transformed DAG. + """ + swap_runs = dag.collect_runs(["swap"]) + for swap_run in swap_runs: + # Partition the swap_run into chunks with equal gate arguments + partition = [] + chunk = [] + for i in range(len(swap_run) - 1): + chunk.append(swap_run[i]) + + qargs0 = swap_run[i].qargs + qargs1 = swap_run[i + 1].qargs + + if qargs0 != qargs1: + partition.append(chunk) + chunk = [] + chunk.append(swap_run[-1]) + partition.append(chunk) + # Simplify each chunk in the partition + for chunk in partition: + if len(chunk) % 2 == 0: + for n in chunk: + dag.remove_op_node(n) + else: + for n in chunk[1:]: + dag.remove_op_node(n) + return dag diff --git a/test/python/transpiler/test_swap_cancellation.py b/test/python/transpiler/test_swap_cancellation.py new file mode 100644 index 000000000000..be09fb7a1182 --- /dev/null +++ b/test/python/transpiler/test_swap_cancellation.py @@ -0,0 +1,45 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +"""Tests for pass cancelling 2 consecutive SWAPs on the same qubits.""" + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import SWAPCancellation +from qiskit.test import QiskitTestCase + + +class TestSWAPCancellation(QiskitTestCase): + """Test the SWAPCancellation pass.""" + + def test_pass_swap_cancellation(self): + """Test the swap cancellation pass. + + It should cancel consecutive swap pairs on same qubits. + """ + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.h(qr[0]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + + pass_manager = PassManager() + pass_manager.append(SWAPCancellation()) + out_circuit = pass_manager.run(circuit) + resources_after = out_circuit.count_ops() + + self.assertNotIn('cx', resources_after) From a043e69172036141f7329208d19f3d74b35118c5 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 22:38:40 +0100 Subject: [PATCH 2/4] more testing --- .../passes/optimization/swap_cancellation.py | 2 +- .../transpiler/test_swap_cancellation.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/swap_cancellation.py b/qiskit/transpiler/passes/optimization/swap_cancellation.py index 98acea5c2ca1..604edbf09d41 100644 --- a/qiskit/transpiler/passes/optimization/swap_cancellation.py +++ b/qiskit/transpiler/passes/optimization/swap_cancellation.py @@ -19,7 +19,7 @@ class SWAPCancellation(TransformationPass): """Cancel back-to-back `swap` gates in dag.""" def run(self, dag): - """Run the CXCancellation pass on `dag`. + """Run the SWAPCancellation pass on `dag`. Args: dag (DAGCircuit): the directed acyclic graph to run on. diff --git a/test/python/transpiler/test_swap_cancellation.py b/test/python/transpiler/test_swap_cancellation.py index be09fb7a1182..4f6e0b9b253c 100644 --- a/test/python/transpiler/test_swap_cancellation.py +++ b/test/python/transpiler/test_swap_cancellation.py @@ -21,7 +21,21 @@ class TestSWAPCancellation(QiskitTestCase): """Test the SWAPCancellation pass.""" - def test_pass_swap_cancellation(self): + def test_pass_swap_cancellation_simple(self): + """Test a simple swap cancellation pass, symmetric wires """ + qr = QuantumRegister(2) + circuit = QuantumCircuit(qr) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + + pass_manager = PassManager() + pass_manager.append(SWAPCancellation()) + out_circuit = pass_manager.run(circuit) + resources_after = out_circuit.count_ops() + + self.assertNotIn('swap', resources_after) + + def test_pass_swap_cancellation_many(self): """Test the swap cancellation pass. It should cancel consecutive swap pairs on same qubits. @@ -42,4 +56,4 @@ def test_pass_swap_cancellation(self): out_circuit = pass_manager.run(circuit) resources_after = out_circuit.count_ops() - self.assertNotIn('cx', resources_after) + self.assertNotIn('swap', resources_after) From 967499d5553c101e78bc526ec9fd69db91285cd7 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sat, 27 Feb 2021 21:22:59 +0100 Subject: [PATCH 3/4] swap is symmetric --- qiskit/transpiler/passes/optimization/swap_cancellation.py | 2 +- test/python/transpiler/test_swap_cancellation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/swap_cancellation.py b/qiskit/transpiler/passes/optimization/swap_cancellation.py index 604edbf09d41..b8cb703955fe 100644 --- a/qiskit/transpiler/passes/optimization/swap_cancellation.py +++ b/qiskit/transpiler/passes/optimization/swap_cancellation.py @@ -38,7 +38,7 @@ def run(self, dag): qargs0 = swap_run[i].qargs qargs1 = swap_run[i + 1].qargs - if qargs0 != qargs1: + if set(qargs0) != set(qargs1): partition.append(chunk) chunk = [] chunk.append(swap_run[-1]) diff --git a/test/python/transpiler/test_swap_cancellation.py b/test/python/transpiler/test_swap_cancellation.py index 4f6e0b9b253c..1aa37a11b534 100644 --- a/test/python/transpiler/test_swap_cancellation.py +++ b/test/python/transpiler/test_swap_cancellation.py @@ -26,7 +26,7 @@ def test_pass_swap_cancellation_simple(self): qr = QuantumRegister(2) circuit = QuantumCircuit(qr) circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[2]) + circuit.swap(qr[1], qr[0]) pass_manager = PassManager() pass_manager.append(SWAPCancellation()) From 0fa3d666773f7cf2dc00a69e7b6ccfddacc02b5f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sun, 28 Feb 2021 09:35:01 +0100 Subject: [PATCH 4/4] simpler test --- .../transpiler/test_swap_cancellation.py | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/test/python/transpiler/test_swap_cancellation.py b/test/python/transpiler/test_swap_cancellation.py index 1aa37a11b534..6cf40d2e4417 100644 --- a/test/python/transpiler/test_swap_cancellation.py +++ b/test/python/transpiler/test_swap_cancellation.py @@ -12,8 +12,7 @@ """Tests for pass cancelling 2 consecutive SWAPs on the same qubits.""" -from qiskit import QuantumRegister, QuantumCircuit -from qiskit.transpiler import PassManager +from qiskit import QuantumCircuit from qiskit.transpiler.passes import SWAPCancellation from qiskit.test import QiskitTestCase @@ -23,14 +22,11 @@ class TestSWAPCancellation(QiskitTestCase): def test_pass_swap_cancellation_simple(self): """Test a simple swap cancellation pass, symmetric wires """ - qr = QuantumRegister(2) - circuit = QuantumCircuit(qr) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[0]) - - pass_manager = PassManager() - pass_manager.append(SWAPCancellation()) - out_circuit = pass_manager.run(circuit) + circuit = QuantumCircuit(2) + circuit.swap(0, 1) + circuit.swap(1, 0) + + out_circuit = SWAPCancellation()(circuit) resources_after = out_circuit.count_ops() self.assertNotIn('swap', resources_after) @@ -40,20 +36,17 @@ def test_pass_swap_cancellation_many(self): It should cancel consecutive swap pairs on same qubits. """ - qr = QuantumRegister(2) - circuit = QuantumCircuit(qr) - circuit.h(qr[0]) - circuit.h(qr[0]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[0]) - circuit.swap(qr[1], qr[0]) - - pass_manager = PassManager() - pass_manager.append(SWAPCancellation()) - out_circuit = pass_manager.run(circuit) + circuit = QuantumCircuit(2) + circuit.h(0) + circuit.h(0) + circuit.swap(0, 1) + circuit.swap(0, 1) + circuit.swap(0, 1) + circuit.swap(0, 1) + circuit.swap(1, 0) + circuit.swap(1, 0) + + out_circuit = SWAPCancellation()(circuit) resources_after = out_circuit.count_ops() self.assertNotIn('swap', resources_after)