From e470693343b8bf5e6b3ec672fb012ffea4d0e771 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 1 Sep 2024 03:33:13 -0500 Subject: [PATCH 01/14] add rust code for remove_diagonal_gates_before_measure --- .../remove_diagonal_gates_before_measure.rs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 crates/accelerate/src/remove_diagonal_gates_before_measure.rs diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs new file mode 100644 index 000000000000..6508f81d1b95 --- /dev/null +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -0,0 +1,96 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// 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 diagonal gates (including diagonal 2Q gates) before a measurement. +use pyo3::prelude::*; +use std::collections::HashSet; + +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::operations::Operation; +use qiskit_circuit::operations::StandardGate; + +/// Run the RemoveDiagonalGatesBeforeMeasure pass on `dag`. +/// Args: +/// dag (DAGCircuit): the DAG to be optimized. +/// Returns: +/// DAGCircuit: the optimized DAG. +#[pyfunction] +#[pyo3(name = "remove_diagonal_gates_before_measure")] +fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyResult<()> { + let diagonal_1q_gates = HashSet::from([ + StandardGate::ZGate, + StandardGate::TGate, + StandardGate::SGate, + StandardGate::TdgGate, + StandardGate::SdgGate, + StandardGate::U1Gate, + ]); + let diagonal_2q_gates = HashSet::from([ + StandardGate::CZGate, + StandardGate::CRZGate, + StandardGate::CU1Gate, + StandardGate::RZZGate, + ]); + + let mut nodes_to_remove = HashSet::new(); + for index in dag.op_nodes(true) { + let node = &dag.dag[index]; + let NodeType::Operation(inst) = node else {panic!()}; + + if inst.op.name() == "measure" { + let predecessor = (dag.quantum_predecessors(index)) + .next() + .expect("index is an operation node, so it must have a predecessor."); + + match &dag.dag[predecessor] { + NodeType::Operation(pred_inst) => match pred_inst.standard_gate() { + None => { + continue; + } + Some(gate) => { + if diagonal_1q_gates.contains(&gate) { + nodes_to_remove.insert(predecessor); + } else if diagonal_2q_gates.contains(&gate) { + let successors = dag.quantum_successors(predecessor); + let mut remove_s = false; + for s in successors { + let node_s = &dag.dag[s]; + let NodeType::Operation(inst_s) = node_s else {panic!()}; + if inst_s.op.name() == "measure" { + remove_s = true; + } + } + if remove_s { + nodes_to_remove.insert(predecessor); + } + } + } + }, + _ => { + continue; + } + } + } + } + + for node_to_remove in nodes_to_remove { + dag.remove_op_node(node_to_remove) + } + + Ok(()) +} + +#[pymodule] +pub fn remove_diagonal_gates_before_measure(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(run_remove_diagonal_before_measure))?; + Ok(()) +} From 285b54b193de683730a12df666cafcc311453f9b Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 1 Sep 2024 03:34:26 -0500 Subject: [PATCH 02/14] replace python code by rust code for remove_diagonal_gates_brefore_measure --- crates/accelerate/src/lib.rs | 1 + crates/pyext/src/lib.rs | 16 +++++--- qiskit/__init__.py | 3 ++ .../remove_diagonal_gates_before_measure.py | 39 +++---------------- 4 files changed, 20 insertions(+), 39 deletions(-) diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 4e079ea84b57..43574738485a 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -23,6 +23,7 @@ pub mod isometry; pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; +pub mod remove_diagonal_gates_before_measure; pub mod results; pub mod sabre; pub mod sampled_exp_val; diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 04b0c0609347..6e3bd56311b6 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -16,11 +16,12 @@ use qiskit_accelerate::{ convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, - sparse_pauli_op::sparse_pauli_op, star_prerouting::star_prerouting, - stochastic_swap::stochastic_swap, synthesis::synthesis, target_transpiler::target, - two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, - vf2_layout::vf2_layout, + pauli_exp_val::pauli_expval, + remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, results::results, + sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, + star_prerouting::star_prerouting, stochastic_swap::stochastic_swap, synthesis::synthesis, + target_transpiler::target, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, + utils::utils, vf2_layout::vf2_layout, }; #[inline(always)] @@ -48,6 +49,11 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, optimize_1q_gates, "optimize_1q_gates")?; add_submodule(m, pauli_expval, "pauli_expval")?; add_submodule(m, synthesis, "synthesis")?; + add_submodule( + m, + remove_diagonal_gates_before_measure, + "remove_diagonal_gates_before_measure", + )?; add_submodule(m, results, "results")?; add_submodule(m, sabre, "sabre")?; add_submodule(m, sampled_exp_val, "sampled_exp_val")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 6a8df393307e..ed918f7a343c 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -73,6 +73,9 @@ sys.modules["qiskit._accelerate.pauli_expval"] = _accelerate.pauli_expval sys.modules["qiskit._accelerate.qasm2"] = _accelerate.qasm2 sys.modules["qiskit._accelerate.qasm3"] = _accelerate.qasm3 +sys.modules["qiskit._accelerate.remove_diagonal_gates_before_measure"] = ( + _accelerate.remove_diagonal_gates_before_measure +) sys.modules["qiskit._accelerate.results"] = _accelerate.results sys.modules["qiskit._accelerate.sabre"] = _accelerate.sabre sys.modules["qiskit._accelerate.sampled_exp_val"] = _accelerate.sampled_exp_val diff --git a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py index be4c79aa47e6..220fb2039c54 100644 --- a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py +++ b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py @@ -12,24 +12,13 @@ """Remove diagonal gates (including diagonal 2Q gates) before a measurement.""" -from qiskit.circuit import Measure -from qiskit.circuit.library.standard_gates import ( - RZGate, - ZGate, - TGate, - SGate, - TdgGate, - SdgGate, - U1Gate, - CZGate, - CRZGate, - CU1Gate, - RZZGate, -) -from qiskit.dagcircuit import DAGOpNode from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.utils import control_flow +from qiskit._accelerate.remove_diagonal_gates_before_measure import ( + remove_diagonal_gates_before_measure, +) + class RemoveDiagonalGatesBeforeMeasure(TransformationPass): """Remove diagonal gates (including diagonal 2Q gates) before a measurement. @@ -48,22 +37,4 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - diagonal_1q_gates = (RZGate, ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate) - diagonal_2q_gates = (CZGate, CRZGate, CU1Gate, RZZGate) - - nodes_to_remove = set() - for measure in dag.op_nodes(Measure): - predecessor = next(dag.quantum_predecessors(measure)) - - if isinstance(predecessor, DAGOpNode) and isinstance(predecessor.op, diagonal_1q_gates): - nodes_to_remove.add(predecessor) - - if isinstance(predecessor, DAGOpNode) and isinstance(predecessor.op, diagonal_2q_gates): - successors = dag.quantum_successors(predecessor) - if all(isinstance(s, DAGOpNode) and isinstance(s.op, Measure) for s in successors): - nodes_to_remove.add(predecessor) - - for node_to_remove in nodes_to_remove: - dag.remove_op_node(node_to_remove) - - return dag + remove_diagonal_gates_before_measure(dag) From 4a725901b2c2359c7187f1e33eac016153705439 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 1 Sep 2024 03:35:28 -0500 Subject: [PATCH 03/14] make some dag_circuit functions public --- crates/circuit/src/dag_circuit.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index bb5fff5e343a..c1e291f452db 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5131,7 +5131,7 @@ impl DAGCircuit { } } - fn quantum_predecessors(&self, node: NodeIndex) -> impl Iterator + '_ { + pub fn quantum_predecessors(&self, node: NodeIndex) -> impl Iterator + '_ { self.dag .edges_directed(node, Incoming) .filter_map(|e| match e.weight() { @@ -5141,7 +5141,7 @@ impl DAGCircuit { .unique() } - fn quantum_successors(&self, node: NodeIndex) -> impl Iterator + '_ { + pub fn quantum_successors(&self, node: NodeIndex) -> impl Iterator + '_ { self.dag .edges_directed(node, Outgoing) .filter_map(|e| match e.weight() { @@ -5615,7 +5615,7 @@ impl DAGCircuit { /// Remove an operation node n. /// /// Add edges from predecessors to successors. - fn remove_op_node(&mut self, index: NodeIndex) { + pub fn remove_op_node(&mut self, index: NodeIndex) { let mut edge_list: Vec<(NodeIndex, NodeIndex, Wire)> = Vec::new(); for (source, in_weight) in self .dag From 536667bdd531ad736ca076940affa8264e265a52 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 1 Sep 2024 07:25:42 -0500 Subject: [PATCH 04/14] fix some of the tests failures --- .../src/remove_diagonal_gates_before_measure.rs | 16 ++++++++-------- .../remove_diagonal_gates_before_measure.py | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index 6508f81d1b95..97fe225176c5 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -27,6 +27,7 @@ use qiskit_circuit::operations::StandardGate; #[pyo3(name = "remove_diagonal_gates_before_measure")] fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyResult<()> { let diagonal_1q_gates = HashSet::from([ + StandardGate::RZGate, StandardGate::ZGate, StandardGate::TGate, StandardGate::SGate, @@ -61,14 +62,13 @@ fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyRe nodes_to_remove.insert(predecessor); } else if diagonal_2q_gates.contains(&gate) { let successors = dag.quantum_successors(predecessor); - let mut remove_s = false; - for s in successors { - let node_s = &dag.dag[s]; - let NodeType::Operation(inst_s) = node_s else {panic!()}; - if inst_s.op.name() == "measure" { - remove_s = true; - } - } + let remove_s = successors + .map(|s| { + let node_s = &dag.dag[s]; + let NodeType::Operation(inst_s) = node_s else {panic!()}; + inst_s.op.name() + }) + .all(|name| name == "measure"); if remove_s { nodes_to_remove.insert(predecessor); } diff --git a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py index 220fb2039c54..3f72cb4a5cc0 100644 --- a/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py +++ b/qiskit/transpiler/passes/optimization/remove_diagonal_gates_before_measure.py @@ -38,3 +38,4 @@ def run(self, dag): DAGCircuit: the optimized DAG. """ remove_diagonal_gates_before_measure(dag) + return dag From b6dc53dbe63e1c92b1a025e7a86dd419253106f4 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Mon, 2 Sep 2024 03:48:47 -0500 Subject: [PATCH 05/14] fix another failing test, add more diagonal gates --- .../remove_diagonal_gates_before_measure.rs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index 97fe225176c5..c06573406946 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -25,7 +25,7 @@ use qiskit_circuit::operations::StandardGate; /// DAGCircuit: the optimized DAG. #[pyfunction] #[pyo3(name = "remove_diagonal_gates_before_measure")] -fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyResult<()> { +fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { let diagonal_1q_gates = HashSet::from([ StandardGate::RZGate, StandardGate::ZGate, @@ -34,12 +34,16 @@ fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyRe StandardGate::TdgGate, StandardGate::SdgGate, StandardGate::U1Gate, + StandardGate::PhaseGate, ]); let diagonal_2q_gates = HashSet::from([ StandardGate::CZGate, StandardGate::CRZGate, StandardGate::CU1Gate, StandardGate::RZZGate, + StandardGate::CPhaseGate, + StandardGate::CSGate, + StandardGate::CSdgGate, ]); let mut nodes_to_remove = HashSet::new(); @@ -54,9 +58,6 @@ fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyRe match &dag.dag[predecessor] { NodeType::Operation(pred_inst) => match pred_inst.standard_gate() { - None => { - continue; - } Some(gate) => { if diagonal_1q_gates.contains(&gate) { nodes_to_remove.insert(predecessor); @@ -65,15 +66,21 @@ fn run_remove_diagonal_before_measure(_py: Python, dag: &mut DAGCircuit) -> PyRe let remove_s = successors .map(|s| { let node_s = &dag.dag[s]; - let NodeType::Operation(inst_s) = node_s else {panic!()}; - inst_s.op.name() + if let NodeType::Operation(inst_s) = node_s { + inst_s.op.name() == "measure" + } else { + false + } }) - .all(|name| name == "measure"); + .all(|ok_to_remove| ok_to_remove); if remove_s { nodes_to_remove.insert(predecessor); } } } + None => { + continue; + } }, _ => { continue; From 08f5d13ceb441057f5ce4d178c22b8ba48d5d08c Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Mon, 2 Sep 2024 06:39:37 -0500 Subject: [PATCH 06/14] add tests for added diagonal gates: p, cp, cs, csdg --- ...st_remove_diagonal_gates_before_measure.py | 100 +++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py index 499bc86d9cf2..009bbeab19cf 100644 --- a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py +++ b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py @@ -50,6 +50,29 @@ def test_optimize_1rz_1measure(self): self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1phase_1measure(self): + """Remove a single PhaseGate + qr0:--P--m-- qr0:--m- + | | + qr1:-----|-- ==> qr1:--|- + | | + cr0:-----.-- cr0:--.- + """ + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.p(0.1, qr[0]) + circuit.measure(qr[0], cr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1z_1measure(self): """Remove a single ZGate qr0:--Z--m-- qr0:--m- @@ -74,7 +97,7 @@ def test_optimize_1z_1measure(self): self.assertEqual(circuit_to_dag(expected), after) def test_optimize_1t_1measure(self): - """Remove a single TGate, SGate, TdgGate, SdgGate, U1Gate + """Remove a single TGate qr0:--T--m-- qr0:--m- | | qr1:-----|-- ==> qr1:--|- @@ -298,6 +321,56 @@ def test_optimize_1cz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1cs_2measure(self): + """Remove a single CSGate + qr0:-CS--m--- qr0:--m--- + | | | + qr1:--.--|-m- ==> qr1:--|-m- + | | | | + cr0:-----.-.- cr0:--.-.- + """ + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.cs(qr[0], qr[1]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[1], cr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + expected.measure(qr[1], cr[0]) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_optimize_1csdg_2measure(self): + """Remove a single CSdgGate + qr0:-CSdg--m--- qr0:--m--- + | | | + qr1:----.--|-m- ==> qr1:--|-m- + | | | | + cr0:-------.-.- cr0:--.-.- + """ + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.csdg(qr[0], qr[1]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[1], cr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + expected.measure(qr[1], cr[0]) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1crz_2measure(self): """Remove a single CRZGate qr0:-RZ--m--- qr0:--m--- @@ -323,6 +396,31 @@ def test_optimize_1crz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1cp_2measure(self): + """Remove a single CPhaseGate + qr0:-CP--m--- qr0:--m--- + | | | + qr1:--.--|-m- ==> qr1:--|-m- + | | | | + cr0:-----.-.- cr0:--.-.- + """ + qr = QuantumRegister(2, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.cp(0.1, qr[0], qr[1]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[1], cr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + expected.measure(qr[1], cr[0]) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1cu1_2measure(self): """Remove a single CU1Gate qr0:-CU1-m--- qr0:--m--- From f543a6607e12c533ff7795cf8849cdb0f884c8ec Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Mon, 2 Sep 2024 06:55:25 -0500 Subject: [PATCH 07/14] make the lists of diagonal gates into static arrays --- .../src/remove_diagonal_gates_before_measure.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index c06573406946..d6555c91975d 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -12,7 +12,7 @@ /// Remove diagonal gates (including diagonal 2Q gates) before a measurement. use pyo3::prelude::*; -use std::collections::HashSet; +use hashbrown::HashSet; use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; use qiskit_circuit::operations::Operation; @@ -26,7 +26,7 @@ use qiskit_circuit::operations::StandardGate; #[pyfunction] #[pyo3(name = "remove_diagonal_gates_before_measure")] fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { - let diagonal_1q_gates = HashSet::from([ + static DIAGONAL_1Q_GATES: [StandardGate; 8] = [ StandardGate::RZGate, StandardGate::ZGate, StandardGate::TGate, @@ -35,8 +35,8 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { StandardGate::SdgGate, StandardGate::U1Gate, StandardGate::PhaseGate, - ]); - let diagonal_2q_gates = HashSet::from([ + ]; + static DIAGONAL_2Q_GATES: [StandardGate; 7] = [ StandardGate::CZGate, StandardGate::CRZGate, StandardGate::CU1Gate, @@ -44,7 +44,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { StandardGate::CPhaseGate, StandardGate::CSGate, StandardGate::CSdgGate, - ]); + ]; let mut nodes_to_remove = HashSet::new(); for index in dag.op_nodes(true) { @@ -59,9 +59,9 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { match &dag.dag[predecessor] { NodeType::Operation(pred_inst) => match pred_inst.standard_gate() { Some(gate) => { - if diagonal_1q_gates.contains(&gate) { + if DIAGONAL_1Q_GATES.contains(&gate) { nodes_to_remove.insert(predecessor); - } else if diagonal_2q_gates.contains(&gate) { + } else if DIAGONAL_2Q_GATES.contains(&gate) { let successors = dag.quantum_successors(predecessor); let remove_s = successors .map(|s| { From 41047b50e1e68c686ec6cbe0fb632c6ec24f0279 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 3 Sep 2024 11:18:14 -0500 Subject: [PATCH 08/14] formatting --- crates/accelerate/src/remove_diagonal_gates_before_measure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index d6555c91975d..b2a4a901539a 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -11,8 +11,8 @@ // that they have been altered from the originals. /// Remove diagonal gates (including diagonal 2Q gates) before a measurement. -use pyo3::prelude::*; use hashbrown::HashSet; +use pyo3::prelude::*; use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; use qiskit_circuit::operations::Operation; From 4e5789167e033ab9b43d9ee82caf0abe6f61f712 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 4 Sep 2024 04:49:32 -0500 Subject: [PATCH 09/14] change nodes_to_remove type to Vec --- .../src/remove_diagonal_gates_before_measure.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index b2a4a901539a..131509afef22 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -17,6 +17,7 @@ use pyo3::prelude::*; use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; use qiskit_circuit::operations::Operation; use qiskit_circuit::operations::StandardGate; +use rustworkx_core::petgraph::stable_graph::NodeIndex; /// Run the RemoveDiagonalGatesBeforeMeasure pass on `dag`. /// Args: @@ -46,7 +47,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { StandardGate::CSdgGate, ]; - let mut nodes_to_remove = HashSet::new(); + let mut nodes_to_remove = Vec::new(); for index in dag.op_nodes(true) { let node = &dag.dag[index]; let NodeType::Operation(inst) = node else {panic!()}; @@ -60,7 +61,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { NodeType::Operation(pred_inst) => match pred_inst.standard_gate() { Some(gate) => { if DIAGONAL_1Q_GATES.contains(&gate) { - nodes_to_remove.insert(predecessor); + nodes_to_remove.push(predecessor); } else if DIAGONAL_2Q_GATES.contains(&gate) { let successors = dag.quantum_successors(predecessor); let remove_s = successors @@ -74,7 +75,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { }) .all(|ok_to_remove| ok_to_remove); if remove_s { - nodes_to_remove.insert(predecessor); + nodes_to_remove.push(predecessor); } } } @@ -89,7 +90,8 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { } } - for node_to_remove in nodes_to_remove { + let set_nodes_to_remove: HashSet = nodes_to_remove.into_iter().collect(); + for node_to_remove in set_nodes_to_remove { dag.remove_op_node(node_to_remove) } From 3b5034f53fd7a0ec47c2ab0711b68139112a9362 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 5 Sep 2024 02:38:28 -0500 Subject: [PATCH 10/14] remove the HashSet following review --- .../src/remove_diagonal_gates_before_measure.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index 131509afef22..4f5a52d3bd8a 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -11,13 +11,11 @@ // that they have been altered from the originals. /// Remove diagonal gates (including diagonal 2Q gates) before a measurement. -use hashbrown::HashSet; use pyo3::prelude::*; use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; use qiskit_circuit::operations::Operation; use qiskit_circuit::operations::StandardGate; -use rustworkx_core::petgraph::stable_graph::NodeIndex; /// Run the RemoveDiagonalGatesBeforeMeasure pass on `dag`. /// Args: @@ -90,9 +88,10 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { } } - let set_nodes_to_remove: HashSet = nodes_to_remove.into_iter().collect(); - for node_to_remove in set_nodes_to_remove { - dag.remove_op_node(node_to_remove) + for node_to_remove in nodes_to_remove { + if dag.dag.node_weight(node_to_remove).is_some() { + dag.remove_op_node(node_to_remove); + } } Ok(()) From 98fb0b487abb3bef5a55457d228ae15344c775ae Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 5 Sep 2024 04:43:34 -0500 Subject: [PATCH 11/14] add CCZ diagonal gate --- .../accelerate/src/remove_diagonal_gates_before_measure.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index 4f5a52d3bd8a..10916a77fca8 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -44,6 +44,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { StandardGate::CSGate, StandardGate::CSdgGate, ]; + static DIAGONAL_3Q_GATES: [StandardGate; 1] = [StandardGate::CCZGate]; let mut nodes_to_remove = Vec::new(); for index in dag.op_nodes(true) { @@ -60,7 +61,9 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { Some(gate) => { if DIAGONAL_1Q_GATES.contains(&gate) { nodes_to_remove.push(predecessor); - } else if DIAGONAL_2Q_GATES.contains(&gate) { + } else if DIAGONAL_2Q_GATES.contains(&gate) + || DIAGONAL_3Q_GATES.contains(&gate) + { let successors = dag.quantum_successors(predecessor); let remove_s = successors .map(|s| { From 0a4968b7d22645bd350e8e2ef962700f1a223799 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 5 Sep 2024 04:43:58 -0500 Subject: [PATCH 12/14] add tests for CCZ diagonal gate --- ...st_remove_diagonal_gates_before_measure.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py index 009bbeab19cf..f174d227379b 100644 --- a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py +++ b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py @@ -471,6 +471,28 @@ def test_optimize_1rzz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) + def test_optimize_1ccz_3measure(self): + """Remove a single CCZGate + """ + qr = QuantumRegister(3, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.ccz(qr[0], qr[1], qr[2]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[1], cr[0]) + circuit.measure(qr[2], cr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.measure(qr[0], cr[0]) + expected.measure(qr[1], cr[0]) + expected.measure(qr[2], cr[0]) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + class TestRemoveDiagonalGatesBeforeMeasureOveroptimizations(QiskitTestCase): """Test situations where remove_diagonal_gates_before_measure should not optimize""" @@ -499,6 +521,23 @@ def test_optimize_1cz_1measure(self): self.assertEqual(expected, after) + def test_optimize_1ccz_1measure(self): + """Do not remove a CCZGate because measure happens on only one of the wires + """ + qr = QuantumRegister(3, "qr") + cr = ClassicalRegister(1, "cr") + circuit = QuantumCircuit(qr, cr) + circuit.ccz(qr[0], qr[1], qr[2]) + circuit.measure(qr[1], cr[0]) + dag = circuit_to_dag(circuit) + + expected = deepcopy(dag) + + pass_ = RemoveDiagonalGatesBeforeMeasure() + after = pass_.run(dag) + + self.assertEqual(expected, after) + def test_do_not_optimize_with_conditional(self): """Diagonal gates with conditionals on a measurement target. See https://github.com/Qiskit/qiskit-terra/pull/2208#issuecomment-487238819 From 68c24e41357986839ae8c972e4e21035b903563c Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 5 Sep 2024 04:53:10 -0500 Subject: [PATCH 13/14] add release notes --- ...ve-diagonal-gates-before-measure-86abe39e46d5dad5.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 releasenotes/notes/update-remove-diagonal-gates-before-measure-86abe39e46d5dad5.yaml diff --git a/releasenotes/notes/update-remove-diagonal-gates-before-measure-86abe39e46d5dad5.yaml b/releasenotes/notes/update-remove-diagonal-gates-before-measure-86abe39e46d5dad5.yaml new file mode 100644 index 000000000000..294b4ed5bbae --- /dev/null +++ b/releasenotes/notes/update-remove-diagonal-gates-before-measure-86abe39e46d5dad5.yaml @@ -0,0 +1,8 @@ +--- +features_transpiler: + - | + The :class:`.RemoveDiagonalGatesBeforeMeasure` transpiler pass has been upgraded to + include more diagonal gates: :class:`.PhaseGate`, :class:`.CPhaseGate`, + :class:`.CSGate`, :class:`.CSdgGate` and :class:`.CCZGate`. + In addition, the code of the :class:`.RemoveDiagonalGatesBeforeMeasure` was ported to Rust, + and is now x20 faster for a 20 qubit circuit. From f427c4d82603efb7d3705e08b4c17610e8d9d6a0 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 5 Sep 2024 06:16:51 -0500 Subject: [PATCH 14/14] fix lint --- .../transpiler/test_remove_diagonal_gates_before_measure.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py index f174d227379b..474a586448b8 100644 --- a/test/python/transpiler/test_remove_diagonal_gates_before_measure.py +++ b/test/python/transpiler/test_remove_diagonal_gates_before_measure.py @@ -472,8 +472,7 @@ def test_optimize_1rzz_2measure(self): self.assertEqual(circuit_to_dag(expected), after) def test_optimize_1ccz_3measure(self): - """Remove a single CCZGate - """ + """Remove a single CCZGate""" qr = QuantumRegister(3, "qr") cr = ClassicalRegister(1, "cr") circuit = QuantumCircuit(qr, cr) @@ -522,8 +521,7 @@ def test_optimize_1cz_1measure(self): self.assertEqual(expected, after) def test_optimize_1ccz_1measure(self): - """Do not remove a CCZGate because measure happens on only one of the wires - """ + """Do not remove a CCZGate because measure happens on only one of the wires""" qr = QuantumRegister(3, "qr") cr = ClassicalRegister(1, "cr") circuit = QuantumCircuit(qr, cr)