From 1fc5ba06258467c691622f607bf467f8d9692e83 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 23 Feb 2021 19:04:38 +0100 Subject: [PATCH 01/13] OptimizeSwapBeforeMeasure(all_measurement=True) --- .../passes/optimization/optimize_swap_before_measure.py | 9 +++++++-- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 40a480a3a49e..16ee43c60a69 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -25,6 +25,9 @@ class OptimizeSwapBeforeMeasure(TransformationPass): Transpiler pass to remove swaps in front of measurements by re-targeting the classical bit of the measure instruction. """ + def __init__(self, all_measurement=False): + self.all_measurement = all_measurement + super().__init__() def run(self, dag): """Run the OptimizeSwapBeforeMeasure pass on `dag`. @@ -39,8 +42,10 @@ def run(self, dag): for swap in swaps[::-1]: final_successor = [] for successor in dag.successors(swap): - final_successor.append(successor.type == 'out' or (successor.type == 'op' and - successor.op.name == 'measure')) + is_final_successor = successor.type == 'op' and successor.op.name == 'measure' + if not self.all_measurement: + is_final_successor = is_final_successor or successor.type == 'out' + final_successor.append(is_final_successor) if all(final_successor): # the node swap needs to be removed and, if a measure follows, needs to be adapted swap_qargs = swap.qargs diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 85f6a827e999..b50cf1fcc9ce 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -177,7 +177,7 @@ def _opt_control(property_set): _reset = [RemoveResetInZeroState()] - _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] + _meas = [OptimizeSwapBeforeMeasure(all_measurement=True), RemoveDiagonalGatesBeforeMeasure()] _opt = [ Collect2qBlocks(), From a6d01698191aec7fb62a7307c513cac6768c93fa Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 23 Feb 2021 20:00:22 +0100 Subject: [PATCH 02/13] more testing --- .../test_optimize_swap_before_measure.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 6f15e9ef9e98..9ab641d47d56 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -218,6 +218,85 @@ def test_optimize_overlap_swap(self): self.assertEqual(expected, after) + def test_all_measurement(self): + """OptimizeSwapBeforeMeasure(all_measurement=True) on total measurment + qr0:--X-----m-- + | | + qr1:--X--m--|-- + | | + cr :-----0--1-- + """ + qr = QuantumRegister(2, 'qr') + cr = ClassicalRegister(2, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.measure(qr[1], cr[0]) + circuit.measure(qr[0], cr[1]) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + expected.measure(qr[1], cr[1]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(all_measurement=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) + + def test_all_measurement_skip(self): + """OptimizeSwapBeforeMeasure(all_measurement=True) on no total measurment + qr0:--X----- + | + qr1:--X--m-- + | + cr0:-----.-- + """ + qr = QuantumRegister(2, 'qr') + cr = ClassicalRegister(1, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.measure(qr[1], cr[0]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(all_measurement=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(circuit, after) + + def test_all_measurement_mixed(self): + """OptimizeSwapBeforeMeasure(all_measurement=True) on mixed measurment + qr0:--X----------- + | + qr1:--X--X-----m-- + | | + qr2:-----X--m--|-- + | | + cr :--------0--1-- + """ + qr = QuantumRegister(3, 'qr') + cr = ClassicalRegister(2, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + circuit.measure(qr[2], cr[0]) + circuit.measure(qr[1], cr[1]) + + expected = QuantumCircuit(qr, cr) + expected.swap(qr[0], qr[1]) + expected.measure(qr[1], cr[0]) + expected.measure(qr[2], cr[1]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(all_measurement=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) if __name__ == '__main__': unittest.main() From f25dabb9884c071aaca9873c50a76b7d572668af Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 23 Feb 2021 21:05:54 +0100 Subject: [PATCH 03/13] lint --- test/python/transpiler/test_optimize_swap_before_measure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 9ab641d47d56..d958d7f0738e 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -298,5 +298,6 @@ def test_all_measurement_mixed(self): self.assertEqual(expected, after) + if __name__ == '__main__': unittest.main() From afd7d06bb9dec974975bc66f2ea88b1ab225e3cc Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 23 Feb 2021 21:11:32 +0100 Subject: [PATCH 04/13] reno and docstring ; --- .../passes/optimization/optimize_swap_before_measure.py | 3 +++ ...tary_when_there_are_no_measurments-4c8a989c4428b600.yaml | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 releasenotes/notes/OptimizeSwapBeforeMeasure_preserves_unitary_when_there_are_no_measurments-4c8a989c4428b600.yaml diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 16ee43c60a69..96aa79d390cb 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -24,6 +24,9 @@ class OptimizeSwapBeforeMeasure(TransformationPass): Transpiler pass to remove swaps in front of measurements by re-targeting the classical bit of the measure instruction. + + If `all_measurement` is `True` (default is `False`)`, the SWAP to be removed + has to be measure on both wires. Otherwise, it stays. """ def __init__(self, all_measurement=False): self.all_measurement = all_measurement diff --git a/releasenotes/notes/OptimizeSwapBeforeMeasure_preserves_unitary_when_there_are_no_measurments-4c8a989c4428b600.yaml b/releasenotes/notes/OptimizeSwapBeforeMeasure_preserves_unitary_when_there_are_no_measurments-4c8a989c4428b600.yaml new file mode 100644 index 000000000000..c428257a7a9b --- /dev/null +++ b/releasenotes/notes/OptimizeSwapBeforeMeasure_preserves_unitary_when_there_are_no_measurments-4c8a989c4428b600.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes #4911. Level 3 now calls + :class:`qiskit.transpiler.passes.optimization.optimize_swap_before_measure.OptimizeSwapBeforeMeasure` + with a special parameter to avoid removing SWAP gates when not followed by measurment on both wires. From b7d21362e291a8af6ee2a511da1fa0ed6593856c Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 12:37:36 +0100 Subject: [PATCH 05/13] OptimizeSwapBeforeMeasure only when last measurements --- .../optimize_swap_before_measure.py | 58 +++---- .../test_optimize_swap_before_measure.py | 144 +++++++++++------- 2 files changed, 120 insertions(+), 82 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 40a480a3a49e..3d5098ac7aec 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -16,6 +16,7 @@ from qiskit.circuit import Measure from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler import Layout from qiskit.dagcircuit import DAGCircuit @@ -35,29 +36,34 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - swaps = dag.op_nodes(SwapGate) - for swap in swaps[::-1]: - final_successor = [] - for successor in dag.successors(swap): - final_successor.append(successor.type == 'out' or (successor.type == 'op' and - successor.op.name == 'measure')) - if all(final_successor): - # the node swap needs to be removed and, if a measure follows, needs to be adapted - swap_qargs = swap.qargs - measure_layer = DAGCircuit() - for qreg in dag.qregs.values(): - measure_layer.add_qreg(qreg) - for creg in dag.cregs.values(): - measure_layer.add_creg(creg) - for successor in list(dag.successors(swap)): - if successor.type == 'op' and successor.op.name == 'measure': - # replace measure node with a new one, where qargs is set with the "other" - # swap qarg. - dag.remove_op_node(successor) - old_measure_qarg = successor.qargs[0] - new_measure_qarg = swap_qargs[swap_qargs.index(old_measure_qarg) - 1] - measure_layer.apply_operation_back(Measure(), [new_measure_qarg], - [successor.cargs[0]]) - dag.compose(measure_layer) - dag.remove_op_node(swap) - return dag + + new_dag = DAGCircuit() + new_dag.metadata = dag.metadata + new_dag._global_phase = dag._global_phase + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + for qreg in dag.qregs.values(): + new_dag.add_qreg(qreg) + + _layout = Layout.generate_trivial_layout(*dag.qregs.values()) + _trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + + for node in dag.topological_op_nodes(): + if node.type == 'op': + if isinstance(node.op, SwapGate): + swap = node + final_successor = [] + for successor in dag.successors(swap): + if successor.type == 'op' and successor.op.name == 'measure': + is_final_measure = all([s.type == 'out' + for s in dag.successors(successor)]) + else: + is_final_measure = False + final_successor.append(successor.type == 'out' or is_final_measure) + if all(final_successor): + swap_qargs = [_trivial_layout[_layout[qarg]] for qarg in swap.qargs] + _layout.swap(*swap_qargs) + continue + qargs = [_trivial_layout[_layout[qarg]] for qarg in node.qargs] + new_dag.apply_operation_back(node.op, qargs, node.cargs) + return new_dag diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 6f15e9ef9e98..fd885759132d 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -73,8 +73,87 @@ def test_optimize_1swap_2measure(self): self.assertEqual(circuit_to_dag(expected), after) + def test_cannot_optimize(self): + """ Cannot optimize when swap is not at the end in all of the successors + qr0:--X-----m-- + | | + qr1:--X-[H]-|-- + | + cr0:--------.-- + """ + qr = QuantumRegister(2, 'qr') + cr = ClassicalRegister(1, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.h(qr[1]) + circuit.measure(qr[0], cr[0]) + dag = circuit_to_dag(circuit) + + pass_ = OptimizeSwapBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(circuit), after) + + +class TestOptimizeSwapBeforeMeasureFixedPoint(QiskitTestCase): + """ Test swap-followed-by-measure optimizations in a transpiler, using fixed point. """ + + def test_optimize_undone_swap(self): + """ Remove redundant swap + qr0:--X--X--m-- qr0:--m--- + | | | | + qr1:--X--X--|-- ==> qr1:--|-- + | | + cr0:--------.-- cr0:--.-- + """ + qr = QuantumRegister(2, 'qr') + cr = ClassicalRegister(1, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[0], qr[1]) + circuit.measure(qr[0], cr[0]) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) + + def test_optimize_overlap_swap(self): + """ Remove two swaps that overlap + qr0:--X-------- qr0:--m-- + | | + qr1:--X--X----- qr1:--|-- + | ==> | + qr2:-----X--m-- qr2:--|-- + | | + cr0:--------.-- cr0:--.-- + """ + qr = QuantumRegister(3, 'qr') + cr = ClassicalRegister(1, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + circuit.measure(qr[2], cr[0]) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) + def test_optimize_nswap_nmeasure(self): - """ Remove severals swap affecting multiple measurements + """ Remove several swap affecting multiple measurements ┌─┐ ┌─┐ q_0: ─X──X─────X────┤M├───────────────────────────────── q_0: ──────┤M├─────────────── │ │ │ └╥┘ ┌─┐ ┌─┐└╥┘ @@ -122,7 +201,6 @@ def test_optimize_nswap_nmeasure(self): circuit.swap(2, 3) circuit.swap(5, 6) circuit.measure(range(8), range(8)) - dag = circuit_to_dag(circuit) expected = QuantumCircuit(8, 8) expected.measure(0, 2) @@ -134,35 +212,17 @@ def test_optimize_nswap_nmeasure(self): expected.measure(6, 5) expected.measure(7, 6) - pass_ = OptimizeSwapBeforeMeasure() - after = pass_.run(dag) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_cannot_optimize(self): - """ Cannot optimize when swap is not at the end in all of the successors - qr0:--X-----m-- - | | - qr1:--X-[H]-|-- - | - cr0:--------.-- - """ - qr = QuantumRegister(2, 'qr') - cr = ClassicalRegister(1, 'cr') - circuit = QuantumCircuit(qr, cr) - circuit.swap(qr[0], qr[1]) - circuit.h(qr[1]) - circuit.measure(qr[0], cr[0]) - dag = circuit_to_dag(circuit) - - pass_ = OptimizeSwapBeforeMeasure() - after = pass_.run(dag) + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) - self.assertEqual(circuit_to_dag(circuit), after) + self.assertEqual(expected, after) -class TestOptimizeSwapBeforeMeasureFixedPoint(QiskitTestCase): - """ Test swap-followed-by-measure optimizations in a transpiler, using fixed point. """ +class TestOptimizeSwapBeforeMeasureMidMeasure(QiskitTestCase): + """ Test swap-followed-by-measure optimizations in a transpiler, with mid-measurement.""" def test_optimize_undone_swap(self): """ Remove redundant swap @@ -190,34 +250,6 @@ def test_optimize_undone_swap(self): self.assertEqual(expected, after) - def test_optimize_overlap_swap(self): - """ Remove two swaps that overlap - qr0:--X-------- qr0:--m-- - | | - qr1:--X--X----- qr1:--|-- - | ==> | - qr2:-----X--m-- qr2:--|-- - | | - cr0:--------.-- cr0:--.-- - """ - qr = QuantumRegister(3, 'qr') - cr = ClassicalRegister(1, 'cr') - circuit = QuantumCircuit(qr, cr) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[2]) - circuit.measure(qr[2], cr[0]) - - expected = QuantumCircuit(qr, cr) - expected.measure(qr[0], cr[0]) - - pass_manager = PassManager() - pass_manager.append( - [OptimizeSwapBeforeMeasure(), DAGFixedPoint()], - do_while=lambda property_set: not property_set['dag_fixed_point']) - after = pass_manager.run(circuit) - - self.assertEqual(expected, after) - if __name__ == '__main__': unittest.main() From 7768dc491be17802c163e98b722a3435462933a9 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 13:15:29 +0100 Subject: [PATCH 06/13] test --- .../test_optimize_swap_before_measure.py | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index fd885759132d..51c864212b95 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -225,22 +225,34 @@ class TestOptimizeSwapBeforeMeasureMidMeasure(QiskitTestCase): """ Test swap-followed-by-measure optimizations in a transpiler, with mid-measurement.""" def test_optimize_undone_swap(self): - """ Remove redundant swap - qr0:--X--X--m-- qr0:--m--- - | | | | - qr1:--X--X--|-- ==> qr1:--|-- - | | - cr0:--------.-- cr0:--.-- - """ - qr = QuantumRegister(2, 'qr') - cr = ClassicalRegister(1, 'cr') - circuit = QuantumCircuit(qr, cr) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[0], qr[1]) - circuit.measure(qr[0], cr[0]) - - expected = QuantumCircuit(qr, cr) - expected.measure(qr[0], cr[0]) + """ """ + qr1 = QuantumRegister(1, 'qr1') + qr2 = QuantumRegister(2, 'qr2') + cr = ClassicalRegister(3, 'cr') + circuit = QuantumCircuit(qr1, qr2, cr) + circuit.h(qr1[0]) + circuit.h(qr2[1]) + circuit.swap(qr1[0], qr2[0]) + circuit.measure(qr1[0], cr[0]) + circuit.measure(qr2[0], cr[1]) + circuit.measure(qr2[1], cr[2]) + circuit.cx(qr1[0], qr2[ 1]) + circuit.swap(qr1[0], qr2[0]) + circuit.measure(qr1[0], cr[0]) + circuit.measure(qr2[0], cr[1]) + circuit.measure(qr2[1], cr[2]) + + expected = QuantumCircuit(qr1, qr2, cr) + expected.h(qr1[0]) + expected.h(qr2[1]) + expected.swap(qr1[0], qr2[0]) + expected.measure(qr1[0], cr[0]) + expected.measure(qr2[0], cr[1]) + expected.measure(qr2[1], cr[2]) + expected.cx(qr1[0], qr2[ 1]) + expected.measure(qr2[0], cr[0]) + expected.measure(qr1[0], cr[1]) + expected.measure(qr2[1], cr[2]) pass_manager = PassManager() pass_manager.append( From b90609b9d282ec79a1501f1eedc95cbec333174e Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 13:35:29 +0100 Subject: [PATCH 07/13] small optimization --- .../passes/optimization/optimize_swap_before_measure.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 3d5098ac7aec..7a1a53b36838 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -50,6 +50,7 @@ def run(self, dag): for node in dag.topological_op_nodes(): if node.type == 'op': + qargs = [_trivial_layout[_layout[qarg]] for qarg in node.qargs] if isinstance(node.op, SwapGate): swap = node final_successor = [] @@ -61,9 +62,7 @@ def run(self, dag): is_final_measure = False final_successor.append(successor.type == 'out' or is_final_measure) if all(final_successor): - swap_qargs = [_trivial_layout[_layout[qarg]] for qarg in swap.qargs] - _layout.swap(*swap_qargs) + _layout.swap(*qargs) continue - qargs = [_trivial_layout[_layout[qarg]] for qarg in node.qargs] new_dag.apply_operation_back(node.op, qargs, node.cargs) return new_dag From a83254670688778b59237ddc0ccac094059c229d Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 13:57:19 +0100 Subject: [PATCH 08/13] docstring --- .../transpiler/test_optimize_swap_before_measure.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 51c864212b95..d18b13cb20c1 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -222,10 +222,10 @@ def test_optimize_nswap_nmeasure(self): class TestOptimizeSwapBeforeMeasureMidMeasure(QiskitTestCase): - """ Test swap-followed-by-measure optimizations in a transpiler, with mid-measurement.""" + """ Test swap-followed-by-measure optimizations, with mid-circuit measurement.""" - def test_optimize_undone_swap(self): - """ """ + def test_mid_circuit(self): + """Test mid-circuit measurement""" qr1 = QuantumRegister(1, 'qr1') qr2 = QuantumRegister(2, 'qr2') cr = ClassicalRegister(3, 'cr') @@ -235,12 +235,10 @@ def test_optimize_undone_swap(self): circuit.swap(qr1[0], qr2[0]) circuit.measure(qr1[0], cr[0]) circuit.measure(qr2[0], cr[1]) - circuit.measure(qr2[1], cr[2]) circuit.cx(qr1[0], qr2[ 1]) circuit.swap(qr1[0], qr2[0]) circuit.measure(qr1[0], cr[0]) circuit.measure(qr2[0], cr[1]) - circuit.measure(qr2[1], cr[2]) expected = QuantumCircuit(qr1, qr2, cr) expected.h(qr1[0]) @@ -248,11 +246,9 @@ def test_optimize_undone_swap(self): expected.swap(qr1[0], qr2[0]) expected.measure(qr1[0], cr[0]) expected.measure(qr2[0], cr[1]) - expected.measure(qr2[1], cr[2]) expected.cx(qr1[0], qr2[ 1]) expected.measure(qr2[0], cr[0]) expected.measure(qr1[0], cr[1]) - expected.measure(qr2[1], cr[2]) pass_manager = PassManager() pass_manager.append( From 79c683761a79d3fbc43049321ac9c6e0e43f7a48 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 14:02:17 +0100 Subject: [PATCH 09/13] move_swap --- .../optimize_swap_before_measure.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 96aa79d390cb..04ec6b0101ef 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -20,16 +20,21 @@ class OptimizeSwapBeforeMeasure(TransformationPass): - """Remove the swaps followed by measurement (and adapt the measurement). + """Remove or move the swaps followed by measurement (and adapt the measurement).""" - Transpiler pass to remove swaps in front of measurements by re-targeting - the classical bit of the measure instruction. + def __init__(self, all_measurement=False, move_swap=False): + """Remove/move the swaps followed by measurement (and adapt the measurement). - If `all_measurement` is `True` (default is `False`)`, the SWAP to be removed - has to be measure on both wires. Otherwise, it stays. - """ - def __init__(self, all_measurement=False): + Transpiler pass to remove swaps in front of measurements by re-targeting + the classical bit of the measure instruction. + + Args: + all_measurement (bool): If `True` (default is `False`)`, the SWAP to be removed + has to be measure on both wires. Otherwise, it stays. + move_swap (bool): + """ self.all_measurement = all_measurement + self.move_swap = move_swap super().__init__() def run(self, dag): @@ -45,10 +50,12 @@ def run(self, dag): for swap in swaps[::-1]: final_successor = [] for successor in dag.successors(swap): - is_final_successor = successor.type == 'op' and successor.op.name == 'measure' + is_measure = successor.type == 'op' and successor.op.name == 'measure' if not self.all_measurement: - is_final_successor = is_final_successor or successor.type == 'out' - final_successor.append(is_final_successor) + is_out = successor.type == 'out' + else: + is_out = False + final_successor.append(is_measure or is_out) if all(final_successor): # the node swap needs to be removed and, if a measure follows, needs to be adapted swap_qargs = swap.qargs @@ -66,6 +73,7 @@ def run(self, dag): new_measure_qarg = swap_qargs[swap_qargs.index(old_measure_qarg) - 1] measure_layer.apply_operation_back(Measure(), [new_measure_qarg], [successor.cargs[0]]) + # measure_layer.apply_operation_back(SwapGate(), swap.qargs) dag.compose(measure_layer) dag.remove_op_node(swap) return dag From 8cad099ee08d604a3f04b9bc5473557ae0e85f75 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 20:36:33 +0100 Subject: [PATCH 10/13] move swaps --- .../optimize_swap_before_measure.py | 50 +++++-- .../test_optimize_swap_before_measure.py | 129 +++++++++++++++++- 2 files changed, 162 insertions(+), 17 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 955cd86aec39..29ae4c2cc153 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -58,21 +58,45 @@ def run(self, dag): _layout = Layout.generate_trivial_layout(*dag.qregs.values()) _trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) - for node in dag.topological_op_nodes(): + nodes = dag.topological_op_nodes() + nodes_to_skip = [] + for node in nodes: + if node in nodes_to_skip: + nodes_to_skip.remove(node) + continue if node.type == 'op': qargs = [_trivial_layout[_layout[qarg]] for qarg in node.qargs] - if isinstance(node.op, SwapGate): - swap = node - final_successor = [] - for successor in dag.successors(swap): - if successor.type == 'op' and successor.op.name == 'measure': - is_final_measure = all([s.type == 'out' - for s in dag.successors(successor)]) - else: - is_final_measure = False - final_successor.append(successor.type == 'out' or is_final_measure) - if all(final_successor): + swap_successors = list(dag.successors(node)) + if isinstance(node.op, SwapGate) and self.should_remove_swap(swap_successors, dag): + if self.move_swap: + [q1, q2] = [s.qargs[0] for s in swap_successors] + [c1, c2] = [s.cargs[0] for s in swap_successors] + new_dag.apply_operation_back(Measure(), [q1], [c2]) + new_dag.apply_operation_back(Measure(), [q2], [c1]) + new_dag.apply_operation_back(SwapGate(), qargs, node.cargs) + nodes_to_skip += swap_successors # skip the successors (they are measure) + else: _layout.swap(*qargs) - continue + continue new_dag.apply_operation_back(node.op, qargs, node.cargs) return new_dag + + def should_remove_swap(self, swap_successors, dag): + final_successor = [] + followed_by_measures = [] + for successor in swap_successors: + is_final_measure = False + if successor.type == 'op' and successor.op.name == 'measure': + followed_by_measures.append(True) + if self.move_swap: + is_final_measure = True + else: + is_final_measure = all([s.type == 'out' for s in dag.successors(successor)]) + else: + followed_by_measures.append(False) + final_successor.append(successor.type == 'out' or is_final_measure) + if self.all_measurement: + return all(followed_by_measures) and all(final_successor) + if self.move_swap: + return all(followed_by_measures) + return all(final_successor) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 496153a35fdb..152b7eee6d06 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -178,6 +178,7 @@ def test_all_measurement(self): after = pass_manager.run(circuit) self.assertEqual(expected, after) + def test_optimize_nswap_nmeasure(self): """ Remove several swap affecting multiple measurements ┌─┐ ┌─┐ @@ -247,7 +248,7 @@ def test_optimize_nswap_nmeasure(self): self.assertEqual(expected, after) def test_all_measurement_skip(self): - """OptimizeSwapBeforeMeasure(all_measurement=True) on no total measurment + """OptimizeSwapBeforeMeasure(all_measurement=True) on no total measurements qr0:--X----- | qr1:--X--m-- @@ -269,7 +270,7 @@ def test_all_measurement_skip(self): self.assertEqual(circuit, after) def test_all_measurement_mixed(self): - """OptimizeSwapBeforeMeasure(all_measurement=True) on mixed measurment + """OptimizeSwapBeforeMeasure(all_measurement=True) on mixed measurement qr0:--X----------- | qr1:--X--X-----m-- @@ -314,7 +315,7 @@ def test_mid_circuit(self): circuit.swap(qr1[0], qr2[0]) circuit.measure(qr1[0], cr[0]) circuit.measure(qr2[0], cr[1]) - circuit.cx(qr1[0], qr2[ 1]) + circuit.cx(qr1[0], qr2[1]) circuit.swap(qr1[0], qr2[0]) circuit.measure(qr1[0], cr[0]) circuit.measure(qr2[0], cr[1]) @@ -325,7 +326,7 @@ def test_mid_circuit(self): expected.swap(qr1[0], qr2[0]) expected.measure(qr1[0], cr[0]) expected.measure(qr2[0], cr[1]) - expected.cx(qr1[0], qr2[ 1]) + expected.cx(qr1[0], qr2[1]) expected.measure(qr2[0], cr[0]) expected.measure(qr1[0], cr[1]) @@ -337,6 +338,126 @@ def test_mid_circuit(self): self.assertEqual(expected, after) + def test_all_measurement_remove(self): + """OptimizeSwapBeforeMeasure(all_measurement=True) with mid-circ measurements, remove one + qr0:--X-----------H----------- + | + qr1:--X--X-----m--X--X--m----- + | | | | + qr2:-----X--m--|--H--X--|--m-- + | | | | + cr :--------0--1--------1--0-- + + Only the last swap should be removed + """ + circuit = QuantumCircuit(3, 2) + circuit.swap(0, 1) + circuit.swap(1, 2) + circuit.measure(2, 0) + circuit.measure(1, 1) + circuit.h(0) + circuit.x(1) + circuit.h(2) + circuit.swap(1, 2) + circuit.measure(1, 1) + circuit.measure(2, 0) + + expected = QuantumCircuit(3, 2) + expected.swap(0, 1) + expected.swap(1, 2) + expected.measure(2, 0) + expected.measure(1, 1) + expected.h(0) + expected.x(1) + expected.h(2) + expected.measure(2, 1) + expected.measure(1, 0) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(all_measurement=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) + + def test_all_measurement_remove(self): + """OptimizeSwapBeforeMeasure(all_measurement=True) with mid-circ measurements, remove none + qr0:--X-----------H-------- + | + qr1:--X--X-----m--X--X--m-- + | | | | + qr2:-----X--m--|--H--X--|-- + | | | + cr :--------0--1--------1-- + + Last swap should stay, because is partially measured + """ + qr = QuantumRegister(3, 'qr') + cr = ClassicalRegister(2, 'cr') + circuit = QuantumCircuit(qr, cr) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + circuit.measure(qr[2], cr[0]) + circuit.measure(qr[1], cr[1]) + circuit.h(qr[0]) + circuit.x(qr[1]) + circuit.h(qr[2]) + circuit.swap(qr[1], qr[2]) + circuit.measure(qr[1], cr[1]) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(all_measurement=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(circuit, after) + + def test_move_swap(self): + """OptimizeSwapBeforeMeasure(move_swap=True) with mid-circ measurements + qr0:--X-----------H----------- + | + qr1:--X--X-----m--X--X--m----- + | | | | + qr2:-----X--m--|--H--X--|--m-- + | | | | + cr :--------0--1--------1--0-- + + Only the last swap should be removed + """ + circuit = QuantumCircuit(3, 2) + circuit.swap(0, 1) + circuit.swap(1, 2) + circuit.measure(2, 0) + circuit.measure(1, 1) + circuit.h(0) + circuit.x(1) + circuit.h(2) + circuit.swap(1, 2) + circuit.measure(1, 1) + circuit.measure(2, 0) + + expected = QuantumCircuit(3, 2) + expected.swap(0, 1) + expected.measure(1, 0) + expected.measure(2, 1) + expected.swap(1, 2) + expected.h(0) + expected.x(1) + expected.h(2) + expected.measure(2, 1) + expected.measure(1, 0) + expected.swap(1, 2) + + pass_manager = PassManager() + pass_manager.append( + [OptimizeSwapBeforeMeasure(move_swap=True), DAGFixedPoint()], + do_while=lambda property_set: not property_set['dag_fixed_point']) + after = pass_manager.run(circuit) + + self.assertEqual(expected, after) + if __name__ == '__main__': unittest.main() From a4dea1df3a1851d9c5f7c6937edf733bac56030e Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 20:37:49 +0100 Subject: [PATCH 11/13] docstring --- .../passes/optimization/optimize_swap_before_measure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 29ae4c2cc153..9b7e9889afef 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -32,7 +32,8 @@ def __init__(self, all_measurement=False, move_swap=False): Args: all_measurement (bool): If `True` (default is `False`)`, the SWAP to be removed has to be measure on both wires. Otherwise, it stays. - move_swap (bool): + move_swap (bool): If `True`, it moves the swap gate behind the measures instead of + removing it. """ self.all_measurement = all_measurement self.move_swap = move_swap From 810633fa2ef1530aab7af486b6e024c116a1382b Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 25 Feb 2021 22:30:59 +0100 Subject: [PATCH 12/13] linting --- .../passes/optimization/optimize_swap_before_measure.py | 3 ++- test/python/transpiler/test_optimize_swap_before_measure.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index 9b7e9889afef..f13520d1674a 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -83,6 +83,7 @@ def run(self, dag): return new_dag def should_remove_swap(self, swap_successors, dag): + """Based on the swap successor characteristics, should that swap be removed/moved? """ final_successor = [] followed_by_measures = [] for successor in swap_successors: @@ -92,7 +93,7 @@ def should_remove_swap(self, swap_successors, dag): if self.move_swap: is_final_measure = True else: - is_final_measure = all([s.type == 'out' for s in dag.successors(successor)]) + is_final_measure = all(s.type == 'out' for s in dag.successors(successor)) else: followed_by_measures.append(False) final_successor.append(successor.type == 'out' or is_final_measure) diff --git a/test/python/transpiler/test_optimize_swap_before_measure.py b/test/python/transpiler/test_optimize_swap_before_measure.py index 152b7eee6d06..c0b49bf9e9dc 100644 --- a/test/python/transpiler/test_optimize_swap_before_measure.py +++ b/test/python/transpiler/test_optimize_swap_before_measure.py @@ -338,7 +338,7 @@ def test_mid_circuit(self): self.assertEqual(expected, after) - def test_all_measurement_remove(self): + def test_all_measurement_remove_one(self): """OptimizeSwapBeforeMeasure(all_measurement=True) with mid-circ measurements, remove one qr0:--X-----------H----------- | @@ -381,7 +381,7 @@ def test_all_measurement_remove(self): self.assertEqual(expected, after) - def test_all_measurement_remove(self): + def test_all_measurement_remove_none(self): """OptimizeSwapBeforeMeasure(all_measurement=True) with mid-circ measurements, remove none qr0:--X-----------H-------- | From 1dd3e96eead2d43c70e05343930d8b8e2b512021 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 3 Mar 2021 14:55:17 -0500 Subject: [PATCH 13/13] Update qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py Co-authored-by: Ali Javadi-Abhari --- .../passes/optimization/optimize_swap_before_measure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py index f13520d1674a..1d4ff4a1d6da 100644 --- a/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py +++ b/qiskit/transpiler/passes/optimization/optimize_swap_before_measure.py @@ -31,7 +31,7 @@ def __init__(self, all_measurement=False, move_swap=False): Args: all_measurement (bool): If `True` (default is `False`)`, the SWAP to be removed - has to be measure on both wires. Otherwise, it stays. + has to be measured on both wires. Otherwise, it stays. move_swap (bool): If `True`, it moves the swap gate behind the measures instead of removing it. """