From 7dfb75499103a9f8fadda47d58fc4907fe638c84 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Thu, 16 Nov 2023 22:03:14 -0500 Subject: [PATCH 1/7] Add RemoveFinalReset pass --- qiskit/transpiler/passes/__init__.py | 2 + .../passes/optimization/__init__.py | 1 + .../passes/optimization/remove_final_reset.py | 37 ++++++ .../preset_passmanagers/builtin_plugins.py | 3 + .../transpiler/preset_passmanagers/common.py | 1 + .../remove-final-reset-488247c01c4e147d.yaml | 37 ++++++ test/python/compiler/test_transpiler.py | 1 + .../transpiler/test_remove_final_reset.py | 114 ++++++++++++++++++ .../test_remove_reset_in_zero_state.py | 2 +- 9 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 qiskit/transpiler/passes/optimization/remove_final_reset.py create mode 100644 releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml create mode 100644 test/python/transpiler/test_remove_final_reset.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 8568b8eac1a2..f67af50c020f 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -83,6 +83,7 @@ Optimize1qGatesSimpleCommutation RemoveDiagonalGatesBeforeMeasure RemoveResetInZeroState + RemoveFinalReset CrosstalkAdaptiveSchedule HoareOptimizer TemplateOptimization @@ -224,6 +225,7 @@ from .optimization import Optimize1qGatesSimpleCommutation from .optimization import OptimizeSwapBeforeMeasure from .optimization import RemoveResetInZeroState +from .optimization import RemoveFinalReset from .optimization import RemoveDiagonalGatesBeforeMeasure from .optimization import CrosstalkAdaptiveSchedule from .optimization import HoareOptimizer diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 291fd9aec58b..9bdee282d0bf 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -24,6 +24,7 @@ from .optimize_1q_commutation import Optimize1qGatesSimpleCommutation from .optimize_swap_before_measure import OptimizeSwapBeforeMeasure from .remove_reset_in_zero_state import RemoveResetInZeroState +from .remove_final_reset import RemoveFinalReset from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure from .crosstalk_adaptive_schedule import CrosstalkAdaptiveSchedule from .hoare_opt import HoareOptimizer diff --git a/qiskit/transpiler/passes/optimization/remove_final_reset.py b/qiskit/transpiler/passes/optimization/remove_final_reset.py new file mode 100644 index 000000000000..5eddb7d6e17e --- /dev/null +++ b/qiskit/transpiler/passes/optimization/remove_final_reset.py @@ -0,0 +1,37 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2023. +# +# 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. + +"""Remove reset when it is the final instruction on a qubit.""" + +from qiskit.circuit import Reset +from qiskit.dagcircuit import DAGOutNode +from qiskit.transpiler.basepasses import TransformationPass + + +class RemoveFinalReset(TransformationPass): + """Remove reset when it is the final instruction on a qubit wire.""" + + def run(self, dag): + """Run the RemoveFinalReset pass on `dag`. + + Args: + dag (DAGCircuit): the DAG to be optimized. + + Returns: + DAGCircuit: the optimized DAG. + """ + resets = dag.op_nodes(Reset) + for reset in resets: + successor = next(dag.successors(reset)) + if isinstance(successor, DAGOutNode): + dag.remove_op_node(reset) + return dag diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ca21bd2a3b57..5f732ee7ac64 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -28,6 +28,7 @@ from qiskit.transpiler.passes import CheckMap from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import OptimizeSwapBeforeMeasure +from qiskit.transpiler.passes import RemoveFinalReset from qiskit.transpiler.passes import RemoveDiagonalGatesBeforeMeasure from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.preset_passmanagers.plugin import ( @@ -543,6 +544,8 @@ def _opt_control(property_set): else: raise TranspilerError(f"Invalid optimization_level: {optimization_level}") + _opt.append(RemoveFinalReset()) + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] # Build nested Flow controllers def _unroll_condition(property_set): diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 494aa7a2159e..008870336276 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -41,6 +41,7 @@ from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import RemoveResetInZeroState +from qiskit.transpiler.passes import RemoveFinalReset from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import InstructionDurationCheck diff --git a/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml b/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml new file mode 100644 index 000000000000..929ac9601aff --- /dev/null +++ b/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml @@ -0,0 +1,37 @@ +--- +features: + - | + Added a new transpiler pass, :class:`.RemoveFinalReset`, which + will remove any :class:`.Reset` operation which is the final + instruction on a qubit wire. For example, taking a circuit with + final :class:`.Reset`\ s: + + .. plot:: + + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(3, 1) + qc.reset(0) + qc.h(range(3)) + qc.cx(1, 0) + qc.measure(0, 0) + qc.reset(range(3)) + qc.draw("mpl") + + will remove the final resets when the pass is run: + + .. plot:: + :include-source: + + from qiskit.transpiler.passes import RemoveFinalReset + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(3, 1) + qc.reset(0) + qc.h(range(3)) + qc.cx(1, 0) + qc.measure(0, 0) + qc.reset(range(3)) + RemoveFinalReset()(qc).draw("mpl") + + This new pass is enabled at optimization levels 1 and higher. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 98db50a28b08..7838f581f81c 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -841,6 +841,7 @@ def test_init_resets_kept_preset_passmanagers(self, optimization_level): num_qubits = 5 qc = QuantumCircuit(num_qubits) qc.reset(range(num_qubits)) + qc.h(range(num_qubits)) num_resets = transpile(qc, optimization_level=optimization_level).count_ops()["reset"] self.assertEqual(num_resets, num_qubits) diff --git a/test/python/transpiler/test_remove_final_reset.py b/test/python/transpiler/test_remove_final_reset.py new file mode 100644 index 000000000000..3796277b73c9 --- /dev/null +++ b/test/python/transpiler/test_remove_final_reset.py @@ -0,0 +1,114 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2023. +# +# 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. + +"""Test RemoveFinalReset pass""" + +import unittest + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import RemoveFinalReset, DAGFixedPoint +from qiskit.converters import circuit_to_dag +from qiskit.test import QiskitTestCase + + +class TestRemoveFinalReset(QiskitTestCase): + """Test remove-reset-in-zero-state optimizations.""" + + def test_optimize_single_reset(self): + """Remove a single final reset + qr0:--[H]--|0>-- ==> qr0:--[H]-- + """ + qr = QuantumRegister(1, "qr") + circuit = QuantumCircuit(qr) + circuit.h(0) + circuit.reset(qr) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.h(0) + + pass_ = RemoveFinalReset() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_dont_optimize_non_final_reset(self): + """Do not remove reset if not final instruction + qr0:--|0>--[H]-- ==> qr0:--|0>--[H]-- + """ + qr = QuantumRegister(1, "qr") + circuit = QuantumCircuit(qr) + circuit.reset(qr) + circuit.h(qr) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.reset(qr) + expected.h(qr) + + pass_ = RemoveFinalReset() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_optimize_single_reset_in_diff_qubits(self): + """Remove a single final reset in different qubits + qr0:--[H]--|0>-- qr0:--[H]-- + ==> + qr1:--[X]--|0>-- qr1:--[X]---- + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.h(0) + circuit.x(1) + circuit.reset(qr) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.h(0) + expected.x(1) + + pass_ = RemoveFinalReset() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + +class TestRemoveFinalResetFixedPoint(QiskitTestCase): + """Test RemoveFinalReset in a transpiler, using fixed point.""" + + def test_two_resets(self): + """Remove two final resets + qr0:--[H]-|0>-|0>-- ==> qr0:--[H]-- + """ + qr = QuantumRegister(1, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr[0]) + circuit.reset(qr[0]) + circuit.reset(qr[0]) + + expected = QuantumCircuit(qr) + expected.h(qr[0]) + + pass_manager = PassManager() + pass_manager.append( + [RemoveFinalReset(), 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() diff --git a/test/python/transpiler/test_remove_reset_in_zero_state.py b/test/python/transpiler/test_remove_reset_in_zero_state.py index 64767d1e07ec..f3a6f548ccd7 100644 --- a/test/python/transpiler/test_remove_reset_in_zero_state.py +++ b/test/python/transpiler/test_remove_reset_in_zero_state.py @@ -22,7 +22,7 @@ class TestRemoveResetInZeroState(QiskitTestCase): - """Test swap-followed-by-measure optimizations.""" + """Test remove-reset-in-zero-state optimizations.""" def test_optimize_single_reset(self): """Remove a single reset From 01ddf91d4f1716e13d4a333fea843f2bfad72fb2 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 17 Nov 2023 15:15:54 -0500 Subject: [PATCH 2/7] Fix lint --- qiskit/transpiler/preset_passmanagers/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 008870336276..494aa7a2159e 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -41,7 +41,6 @@ from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import RemoveResetInZeroState -from qiskit.transpiler.passes import RemoveFinalReset from qiskit.transpiler.passes import ValidatePulseGates from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import InstructionDurationCheck From f358d46c33925f68598b67151caa1b8ba2de5631 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 17 Nov 2023 15:17:29 -0500 Subject: [PATCH 3/7] Look at predecessors of `output_map` Suggested at https://github.com/Qiskit/qiskit/pull/11266#discussion_r1397572149 Co-authored-by: Matthew Treinish --- .../passes/optimization/remove_final_reset.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/remove_final_reset.py b/qiskit/transpiler/passes/optimization/remove_final_reset.py index 5eddb7d6e17e..bb028508fdfb 100644 --- a/qiskit/transpiler/passes/optimization/remove_final_reset.py +++ b/qiskit/transpiler/passes/optimization/remove_final_reset.py @@ -12,8 +12,8 @@ """Remove reset when it is the final instruction on a qubit.""" -from qiskit.circuit import Reset -from qiskit.dagcircuit import DAGOutNode +from qiskit.circuit import Reset, Qubit +from qiskit.dagcircuit import DAGOpNode from qiskit.transpiler.basepasses import TransformationPass @@ -29,9 +29,9 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - resets = dag.op_nodes(Reset) - for reset in resets: - successor = next(dag.successors(reset)) - if isinstance(successor, DAGOutNode): - dag.remove_op_node(reset) + for output_node in dag.output_map.values(): + if isinstance(output_node.wire, Qubit): + pred = next(dag.predecessors(output_node)) + if isinstance(pred, DAGOpNode) and isinstance(pred.op, Reset): + dag.remove_op_node(pred) return dag From 4adc04889f76239e475a2ddd33929fb0d4cf267a Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 21 Nov 2023 16:58:11 -0500 Subject: [PATCH 4/7] Remove `RemoveFinalReset` from preset passmanagers This pass modifies the quantum output state of the circuit, so it should not be run by default. --- qiskit/transpiler/preset_passmanagers/builtin_plugins.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 5f732ee7ac64..ca21bd2a3b57 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -28,7 +28,6 @@ from qiskit.transpiler.passes import CheckMap from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import OptimizeSwapBeforeMeasure -from qiskit.transpiler.passes import RemoveFinalReset from qiskit.transpiler.passes import RemoveDiagonalGatesBeforeMeasure from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.preset_passmanagers.plugin import ( @@ -544,8 +543,6 @@ def _opt_control(property_set): else: raise TranspilerError(f"Invalid optimization_level: {optimization_level}") - _opt.append(RemoveFinalReset()) - unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] # Build nested Flow controllers def _unroll_condition(property_set): From 92ca2da13179fdcd9597244e3aada23fcff38903 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Mon, 18 Mar 2024 21:26:07 -0400 Subject: [PATCH 5/7] Update remove-final-reset-488247c01c4e147d.yaml It's no longer run by default at any optimization level. --- releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml b/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml index 929ac9601aff..090654e16b9d 100644 --- a/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml +++ b/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml @@ -33,5 +33,3 @@ features: qc.measure(0, 0) qc.reset(range(3)) RemoveFinalReset()(qc).draw("mpl") - - This new pass is enabled at optimization levels 1 and higher. From d88605c10c4df387181544475dade2dd70182a11 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 19 Mar 2024 12:16:21 -0400 Subject: [PATCH 6/7] Fix `QiskitTestCase` import --- test/python/transpiler/test_remove_final_reset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_remove_final_reset.py b/test/python/transpiler/test_remove_final_reset.py index 3796277b73c9..371ff3f8405f 100644 --- a/test/python/transpiler/test_remove_final_reset.py +++ b/test/python/transpiler/test_remove_final_reset.py @@ -18,7 +18,7 @@ from qiskit.transpiler import PassManager from qiskit.transpiler.passes import RemoveFinalReset, DAGFixedPoint from qiskit.converters import circuit_to_dag -from qiskit.test import QiskitTestCase +from test import QiskitTestCase # pylint: disable=wrong-import-order class TestRemoveFinalReset(QiskitTestCase): From ed91caafb9fb5983d636d2e93423234910ce1a24 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 19 Mar 2024 12:53:46 -0400 Subject: [PATCH 7/7] Update to use `DoWhileController` --- test/python/transpiler/test_remove_final_reset.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/python/transpiler/test_remove_final_reset.py b/test/python/transpiler/test_remove_final_reset.py index 371ff3f8405f..22ac234a81f5 100644 --- a/test/python/transpiler/test_remove_final_reset.py +++ b/test/python/transpiler/test_remove_final_reset.py @@ -15,6 +15,7 @@ import unittest from qiskit import QuantumRegister, QuantumCircuit +from qiskit.passmanager.flow_controllers import DoWhileController from qiskit.transpiler import PassManager from qiskit.transpiler.passes import RemoveFinalReset, DAGFixedPoint from qiskit.converters import circuit_to_dag @@ -102,8 +103,10 @@ def test_two_resets(self): pass_manager = PassManager() pass_manager.append( - [RemoveFinalReset(), DAGFixedPoint()], - do_while=lambda property_set: not property_set["dag_fixed_point"], + DoWhileController( + [RemoveFinalReset(), DAGFixedPoint()], + do_while=lambda property_set: not property_set["dag_fixed_point"], + ) ) after = pass_manager.run(circuit)