From 22f80b23e3418a11d663e195bfa02d2fa1141847 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Jul 2022 09:35:06 -0400 Subject: [PATCH] Add ResetAfterMeasureSimplification transpiler pass This commit adds a new transpiler pass to simplify resets after a measurement. This pass when run will replace any reset after a measurement with a conditional X gate. This is because the reset operation on IBM backends is implemented by performing a conditional x gate after a reset. So doing this simplification will improve the fidelity of the circuit because we're removing a duplicate measurement which was implicit in the reset. This pass is based on the marz library: https://github.com/Qiskit-Partners/marz which did the same thing but at the QuantumCircuit level. One note is that this pass is basically specific to IBM backends so it's not added to the preset pass managers. Ideally we'd be able to have the IBM backends run this as part of the init stage or something to do the logical transformation early in the compilation. But right now there is no mechanism to do this (see #8329), so for now having the pass and letting users specify it in the pass manager directly is the best option. After #8329 is implemented we can look at adding this pass to that hook interface in the ibm provider's backends directly so that they can leverage this optimization whenever they're the compilation target. --- qiskit/transpiler/passes/__init__.py | 2 + .../passes/optimization/__init__.py | 1 + .../reset_after_measure_simplification.py | 46 ++++++ ...-simplification-pass-82377d80dd0081fd.yaml | 10 ++ ...test_reset_after_measure_simplification.py | 138 ++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py create mode 100644 releasenotes/notes/add-reset-simplification-pass-82377d80dd0081fd.yaml create mode 100644 test/python/transpiler/test_reset_after_measure_simplification.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index e55d7bdfccde..f1472e8b9a56 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -84,6 +84,7 @@ HoareOptimizer TemplateOptimization EchoRZXWeylDecomposition + ResetAfterMeasureSimplification Calibration ============= @@ -218,6 +219,7 @@ from .optimization import InverseCancellation from .optimization import EchoRZXWeylDecomposition from .optimization import CollectLinearFunctions +from .optimization import ResetAfterMeasureSimplification # circuit analysis from .analysis import ResourceEstimation diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 492224d5476c..82743142ba9e 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -31,3 +31,4 @@ from .collect_1q_runs import Collect1qRuns from .echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition from .collect_linear_functions import CollectLinearFunctions +from .reset_after_measure_simplification import ResetAfterMeasureSimplification diff --git a/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py new file mode 100644 index 000000000000..22f1f87e2d79 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/reset_after_measure_simplification.py @@ -0,0 +1,46 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 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. + +"""Replace resets after measure with a conditional XGate.""" + +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.circuit.library.standard_gates.x import XGate +from qiskit.circuit.reset import Reset +from qiskit.circuit.measure import Measure +from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagnode import DAGOpNode + + +class ResetAfterMeasureSimplification(TransformationPass): + """This pass replaces reset after measure with a conditional X gate. + + This optimization is suitable for use on IBM Quantum systems where the + reset operation is performed by a measurement followed by a conditional + x-gate. It might not be desireable on other backends if reset is implemented + differently. + """ + + def run(self, dag): + """Run the pass on a dag.""" + for node in dag.op_nodes(Measure): + succ = next(dag.quantum_successors(node)) + if isinstance(succ, DAGOpNode) and isinstance(succ.op, Reset): + new_x = XGate() + new_x.condition = (node.cargs[0], 1) + new_dag = DAGCircuit() + new_dag.add_qubits(node.qargs) + new_dag.add_clbits(node.cargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs) + new_dag.apply_operation_back(new_x, node.qargs) + dag.remove_op_node(succ) + dag.substitute_node_with_dag(node, new_dag) + return dag diff --git a/releasenotes/notes/add-reset-simplification-pass-82377d80dd0081fd.yaml b/releasenotes/notes/add-reset-simplification-pass-82377d80dd0081fd.yaml new file mode 100644 index 000000000000..d2e5d31f1369 --- /dev/null +++ b/releasenotes/notes/add-reset-simplification-pass-82377d80dd0081fd.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added a new transpiler pass, :class:`~ResetAfterMeasureSimplification`, + which is used to replace a :class:`~.Reset` operation after a + :class:`~.Measure` with a conditional :class:`~.XGate`. For IBM backends + this :class:`~.Reset` operation is performed by doing a measurement and + then a conditional X gate. So if used with an IBM backend this pass will + remove a duplicate implicit :class:`~.Measure` from the :class:`~.Reset` + operation. diff --git a/test/python/transpiler/test_reset_after_measure_simplification.py b/test/python/transpiler/test_reset_after_measure_simplification.py new file mode 100644 index 000000000000..b8f629711e32 --- /dev/null +++ b/test/python/transpiler/test_reset_after_measure_simplification.py @@ -0,0 +1,138 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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 the ResetAfterMeasureSimplification pass""" + +from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister +from qiskit.circuit.classicalregister import Clbit +from qiskit.transpiler.passes.optimization import ResetAfterMeasureSimplification +from qiskit.test import QiskitTestCase + + +class TestResetAfterMeasureSimplificationt(QiskitTestCase): + def test_simple(self): + """Test simple""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + qc.reset(0) + + new_qc = ResetAfterMeasureSimplification()(qc) + + ans_qc = QuantumCircuit(1, 1) + ans_qc.measure(0, 0) + ans_qc.x(0).c_if(ans_qc.clbits[0], 1) + new_qc.draw("mpl", filename="/tmp/foo.png") + self.assertEqual(new_qc, ans_qc) + + def test_simple_null(self): + """Test simple no change in circuit""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + qc.x(0) + qc.reset(0) + new_qc = ResetAfterMeasureSimplification()(qc) + + self.assertEqual(new_qc, qc) + + def test_simple_multi_reg(self): + """Test simple, multiple registers""" + cr1 = ClassicalRegister(1, "c1") + cr2 = ClassicalRegister(1, "c2") + qr = QuantumRegister(1, "q") + qc = QuantumCircuit(qr, cr1, cr2) + qc.measure(0, 1) + qc.reset(0) + + new_qc = ResetAfterMeasureSimplification()(qc) + + ans_qc = QuantumCircuit(qr, cr1, cr2) + ans_qc.measure(0, 1) + ans_qc.x(0).c_if(cr2[0], 1) + + self.assertEqual(new_qc, ans_qc) + + def test_simple_multi_reg_null(self): + """Test simple, multiple registers, null change""" + cr1 = ClassicalRegister(1, "c1") + cr2 = ClassicalRegister(1, "c2") + qr = QuantumRegister(2, "q") + qc = QuantumCircuit(qr, cr1, cr2) + qc.measure(0, 1) + qc.reset(1) # reset not on same qubit as meas + + new_qc = ResetAfterMeasureSimplification()(qc) + self.assertEqual(new_qc, qc) + + def test_simple_multi_resets(self): + """Only first reset is collapsed""" + qc = QuantumCircuit(1, 2) + qc.measure(0, 0) + qc.reset(0) + qc.reset(0) + + new_qc = ResetAfterMeasureSimplification()(qc) + + ans_qc = QuantumCircuit(1, 2) + ans_qc.measure(0, 0) + ans_qc.x(0).c_if(ans_qc.clbits[0], 1) + ans_qc.reset(0) + self.assertEqual(new_qc, ans_qc) + + def test_simple_multi_resets_with_resets_before_measure(self): + """Reset BEFORE measurement not collapsed""" + qc = QuantumCircuit(2, 2) + qc.measure(0, 0) + qc.reset(0) + qc.reset(1) + qc.measure(1, 1) + + new_qc = ResetAfterMeasureSimplification()(qc) + + ans_qc = QuantumCircuit(2, 2) + ans_qc.measure(0, 0) + ans_qc.x(0).c_if(Clbit(ClassicalRegister(2, "c"), 0), 1) + ans_qc.reset(1) + ans_qc.measure(1, 1) + + self.assertEqual(new_qc, ans_qc) + + def test_barriers_work(self): + """Test that barriers block consolidation""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + qc.barrier(0) + qc.reset(0) + + new_qc = ResetAfterMeasureSimplification()(qc) + self.assertEqual(new_qc, qc) + + def test_bv_circuit(self): + bitstring = "11111" + qc = QuantumCircuit(2, len(bitstring)) + qc.x(1) + qc.h(1) + for idx, bit in enumerate(bitstring[::-1]): + qc.h(0) + if int(bit): + qc.cx(0, 1) + qc.h(0) + qc.measure(0, idx) + if idx != len(bitstring) - 1: + qc.reset(0) + # reset control + qc.reset(1) + qc.x(1) + qc.h(1) + new_qc = ResetAfterMeasureSimplification()(qc) + for op in new_qc.data: + if op.operation.name == "reset": + self.assertEqual(op.qubits[0], new_qc.qubits[1])