From d5e9a01894877b6f8e2ecdf7aa7b6b195e96aa71 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 5 Sep 2024 15:54:07 +0300 Subject: [PATCH 01/11] initial commit --- crates/accelerate/src/elide_permutations.rs | 102 ++++++++++++++ crates/accelerate/src/lib.rs | 1 + crates/circuit/src/dag_circuit.rs | 130 ++++++++++++------ crates/pyext/src/lib.rs | 3 +- qiskit/__init__.py | 1 + .../passes/optimization/elide_permutations.py | 41 ++---- ...t-elide-permutations-ed91c3d9cef2fec6.yaml | 9 ++ .../transpiler/test_elide_permutations.py | 17 +++ 8 files changed, 228 insertions(+), 76 deletions(-) create mode 100644 crates/accelerate/src/elide_permutations.rs create mode 100644 releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs new file mode 100644 index 000000000000..1266a33829ab --- /dev/null +++ b/crates/accelerate/src/elide_permutations.rs @@ -0,0 +1,102 @@ +// 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. + +use numpy::PyReadonlyArray1; +use pyo3::prelude::*; + +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::operations::{Operation, Param}; +use qiskit_circuit::Qubit; + +/// Run the ElidePermutations pass on `dag`. +/// Args: +/// dag (DAGCircuit): the DAG to be optimized. +/// Returns: +/// DAGCircuit: the optimized DAG. +#[pyfunction] +fn run(py: Python, dag: &mut DAGCircuit) -> PyResult)>> { + let permutation_gate_names = ["swap".to_string(), "permutation".to_string()]; + let op_counts = dag.count_ops(py, false)?; + if !permutation_gate_names + .iter() + .any(|name| op_counts.contains_key(name)) + { + return Ok(None); + } + let mut mapping: Vec = (0..dag.num_qubits()).collect(); + + // note that DAGCircuit::copy_empty_like clones the interners + let mut new_dag = dag.copy_empty_like(py, "alike")?; + for node_index in dag.topological_op_nodes()? { + if let NodeType::Operation(inst) = &dag.dag[node_index] { + match (inst.op.name(), inst.condition()) { + ("swap", None) => { + let qargs = dag.get_qargs(inst.qubits); + let index0 = qargs[0].0 as usize; + let index1 = qargs[1].0 as usize; + let prev0 = mapping[index0]; + let prev1 = mapping[index1]; + mapping[index0] = prev1; + mapping[index1] = prev0; + } + ("permutation", None) => { + if let Param::Obj(ref pyobj) = inst.params.as_ref().unwrap()[0] { + let pyarray: PyReadonlyArray1 = pyobj.extract(py)?; + let pattern = pyarray.as_array(); + + let qindices: Vec = dag + .get_qargs(inst.qubits) + .iter() + .map(|q| q.0 as usize) + .collect(); + + let remapped_qindices: Vec = (0..qindices.len()) + .map(|i| pattern[i]) + .map(|i| qindices[i as usize]) + .collect(); + + qindices + .iter() + .zip(remapped_qindices.iter()) + .for_each(|(old, new)| { + mapping[*old] = *new; + }); + } else { + unreachable!(); + } + } + _ => { + // General instruction + let mut mapped_inst = inst.clone(); + let qargs = dag.get_qargs(inst.qubits); + let mapped_qargs: Vec = qargs + .iter() + .map(|q| q.0 as usize) + .map(|q| mapping[q]) + .map(|q| Qubit(q.try_into().unwrap())) + .collect(); + let mapped_qubits = new_dag.set_qargs(&mapped_qargs); + mapped_inst.qubits = mapped_qubits; + new_dag.push_back(py, mapped_inst)?; + } + } + } else { + unreachable!("Not an op node") + } + } + Ok(Some((new_dag, mapping))) +} + +pub fn elide_permutations(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(run))?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 6561dd258614..31fec7a7aab6 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -20,6 +20,7 @@ pub mod commutation_checker; pub mod convert_2q_block_matrix; pub mod dense_layout; pub mod edge_collections; +pub mod elide_permutations; pub mod error_map; pub mod euler_one_qubit_decomposer; pub mod isometry; diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index fdaa81e3b4ca..157b53433a74 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1555,7 +1555,7 @@ def _format(operand): /// Returns: /// DAGCircuit: An empty copy of self. #[pyo3(signature = (*, vars_mode="alike"))] - fn copy_empty_like(&self, py: Python, vars_mode: &str) -> PyResult { + pub fn copy_empty_like(&self, py: Python, vars_mode: &str) -> PyResult { let mut target_dag = DAGCircuit::with_capacity( py, self.num_qubits(), @@ -4577,45 +4577,9 @@ def _format(operand): /// /// Returns: /// Mapping[str, int]: a mapping of operation names to the number of times it appears. - #[pyo3(signature = (*, recurse=true))] - fn count_ops(&self, py: Python, recurse: bool) -> PyResult { - if !recurse || !self.has_control_flow() { - Ok(self.op_names.to_object(py)) - } else { - fn inner( - py: Python, - dag: &DAGCircuit, - counts: &mut HashMap, - ) -> PyResult<()> { - for (key, value) in dag.op_names.iter() { - counts - .entry(key.clone()) - .and_modify(|count| *count += value) - .or_insert(*value); - } - let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); - for node in dag.dag.node_weights() { - let NodeType::Operation(node) = node else { - continue; - }; - if !node.op.control_flow() { - continue; - } - let OperationRef::Instruction(inst) = node.op.view() else { - panic!("control flow op must be an instruction") - }; - let blocks = inst.instruction.bind(py).getattr("blocks")?; - for block in blocks.iter()? { - let inner_dag: &DAGCircuit = &circuit_to_dag.call1((block?,))?.extract()?; - inner(py, inner_dag, counts)?; - } - } - Ok(()) - } - let mut counts = HashMap::with_capacity(self.op_names.len()); - inner(py, self, &mut counts)?; - Ok(counts.to_object(py)) - } + #[pyo3(name = "count_ops", signature = (*, recurse=true))] + fn py_count_ops(&self, py: Python, recurse: bool) -> PyResult { + self.count_ops(py, recurse).map(|x| x.into_py(py)) } /// Count the occurrences of operation names on the longest path. @@ -4747,7 +4711,7 @@ def _format(operand): ("qubits", self.num_qubits().into_py(py)), ("bits", self.num_clbits().into_py(py)), ("factors", self.num_tensor_factors().into_py(py)), - ("operations", self.count_ops(py, true)?), + ("operations", self.py_count_ops(py, true)?), ])) } @@ -5151,7 +5115,7 @@ impl DAGCircuit { /// This is mostly used to apply operations from one DAG to /// another that was created from the first via /// [DAGCircuit::copy_empty_like]. - fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { + pub fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { let op_name = instr.op.name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &instr) { @@ -5299,7 +5263,7 @@ impl DAGCircuit { Ok(nodes.into_iter()) } - fn topological_op_nodes(&self) -> PyResult + '_> { + pub fn topological_op_nodes(&self) -> PyResult + '_> { Ok(self.topological_nodes()?.filter(|node: &NodeIndex| { matches!(self.dag.node_weight(*node), Some(NodeType::Operation(_))) })) @@ -6236,6 +6200,16 @@ impl DAGCircuit { self.cargs_interner.get(index) } + /// Insert qargs, return the intern index + pub fn set_qargs(&mut self, qargs: &[Qubit]) -> Interned<[Qubit]> { + self.qargs_interner.insert(qargs) + } + + /// Insert cargs, return the intern index + pub fn set_cargs(&mut self, cargs: &[Clbit]) -> Interned<[Clbit]> { + self.cargs_interner.insert(cargs) + } + /// Insert a new 1q standard gate on incoming qubit pub fn insert_1q_on_incoming_qubit( &mut self, @@ -6347,6 +6321,76 @@ impl DAGCircuit { Err(DAGCircuitError::new_err("Specified node is not an op node")) } } + + pub fn count_ops( + &self, + py: Python, + recurse: bool, + ) -> PyResult> { + if !recurse || !self.has_control_flow() { + Ok(self.op_names.clone()) + } else { + fn inner( + py: Python, + dag: &DAGCircuit, + counts: &mut IndexMap, + ) -> PyResult<()> { + for (key, value) in dag.op_names.iter() { + counts + .entry(key.clone()) + .and_modify(|count| *count += value) + .or_insert(*value); + } + let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); + for node in dag.dag.node_weights() { + let NodeType::Operation(node) = node else { + continue; + }; + if !node.op.control_flow() { + continue; + } + let OperationRef::Instruction(inst) = node.op.view() else { + panic!("control flow op must be an instruction") + }; + let blocks = inst.instruction.bind(py).getattr("blocks")?; + for block in blocks.iter()? { + let inner_dag: &DAGCircuit = &circuit_to_dag.call1((block?,))?.extract()?; + inner(py, inner_dag, counts)?; + } + } + Ok(()) + } + let mut counts = + IndexMap::with_capacity_and_hasher(self.op_names.len(), RandomState::default()); + inner(py, self, &mut counts)?; + Ok(counts) + } + } + + // /// Returns an immutable view of the underlying DAG. + // pub fn dag(&self) -> &StableDiGraph { + // &self.dag + // } + // + // /// Returns an immutable view of the Qubits registered in the circuit + // pub fn qubits(&self) -> &BitData { + // &self.qubits + // } + // + // /// Returns an immutable view of the Classical bits registered in the circuit + // pub fn clbits(&self) -> &BitData { + // &self.clbits + // } + // + // /// Returns an immutable view of the Interner used for Qargs + // pub fn qargs_interner(&self) -> &IndexedInterner> { + // &self.qargs_cache + // } + // + // /// Returns an immutable view of the Interner used for Cargs + // pub fn cargs_interner(&self) -> &IndexedInterner> { + // &self.cargs_cache + // } } /// Add to global phase. Global phase can only be Float or ParameterExpression so this diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index fdb2bff9a21d..e02ad3d9957d 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -15,7 +15,7 @@ use pyo3::prelude::*; use qiskit_accelerate::{ circuit_library::circuit_library, commutation_analysis::commutation_analysis, commutation_checker::commutation_checker, convert_2q_block_matrix::convert_2q_block_matrix, - dense_layout::dense_layout, error_map::error_map, + dense_layout::dense_layout, elide_permutations::elide_permutations, 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, @@ -43,6 +43,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, circuit_library, "circuit_library")?; add_submodule(m, convert_2q_block_matrix, "convert_2q_block_matrix")?; add_submodule(m, dense_layout, "dense_layout")?; + add_submodule(m, elide_permutations, "elide_permutations")?; add_submodule(m, error_map, "error_map")?; add_submodule(m, euler_one_qubit_decomposer, "euler_one_qubit_decomposer")?; add_submodule(m, isometry, "isometry")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 38a9f5952425..6839ba9ab2b2 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,6 +80,7 @@ sys.modules["qiskit._accelerate.sparse_pauli_op"] = _accelerate.sparse_pauli_op sys.modules["qiskit._accelerate.star_prerouting"] = _accelerate.star_prerouting sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap +sys.modules["qiskit._accelerate.elide_permutations"] = _accelerate.elide_permutations sys.modules["qiskit._accelerate.target"] = _accelerate.target sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout diff --git a/qiskit/transpiler/passes/optimization/elide_permutations.py b/qiskit/transpiler/passes/optimization/elide_permutations.py index ca6902f15b43..e9d302d94cbe 100644 --- a/qiskit/transpiler/passes/optimization/elide_permutations.py +++ b/qiskit/transpiler/passes/optimization/elide_permutations.py @@ -15,10 +15,9 @@ import logging -from qiskit.circuit.library.standard_gates import SwapGate -from qiskit.circuit.library.generalized_gates import PermutationGate from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.layout import Layout +from qiskit._accelerate import elide_permutations as elide_permutations_rs logger = logging.getLogger(__name__) @@ -67,38 +66,16 @@ def run(self, dag): ) return dag - op_count = dag.count_ops(recurse=False) - if op_count.get("swap", 0) == 0 and op_count.get("permutation", 0) == 0: + result = elide_permutations_rs.run(dag) + + # If the pass did not do anything, the result is None + if result is None: return dag - new_dag = dag.copy_empty_like() - qubit_mapping = list(range(len(dag.qubits))) - - def _apply_mapping(qargs): - return tuple(dag.qubits[qubit_mapping[dag.find_bit(qubit).index]] for qubit in qargs) - - for node in dag.topological_op_nodes(): - if not isinstance(node.op, (SwapGate, PermutationGate)): - new_dag.apply_operation_back( - node.op, _apply_mapping(node.qargs), node.cargs, check=False - ) - elif getattr(node.op, "condition", None) is not None: - new_dag.apply_operation_back( - node.op, _apply_mapping(node.qargs), node.cargs, check=False - ) - elif isinstance(node.op, SwapGate): - index_0 = dag.find_bit(node.qargs[0]).index - index_1 = dag.find_bit(node.qargs[1]).index - qubit_mapping[index_1], qubit_mapping[index_0] = ( - qubit_mapping[index_0], - qubit_mapping[index_1], - ) - elif isinstance(node.op, PermutationGate): - starting_indices = [qubit_mapping[dag.find_bit(qarg).index] for qarg in node.qargs] - pattern = node.op.params[0] - pattern_indices = [qubit_mapping[idx] for idx in pattern] - for i, j in zip(starting_indices, pattern_indices): - qubit_mapping[i] = j + # Otherwise, the result is a pair consisting of the rewritten DAGCircuit and the + # qubit mapping. + (new_dag, qubit_mapping) = result + input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)} self.property_set["original_layout"] = Layout(input_qubit_mapping) if self.property_set["original_qubit_indices"] is None: diff --git a/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml b/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml new file mode 100644 index 000000000000..3620520ca9f6 --- /dev/null +++ b/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml @@ -0,0 +1,9 @@ +--- +features_transpiler: + - | + Port most of the logic of the transpiler pass :class:`~.ElidePermutations` + to Rust. +fixes: + - | + Fixed a bug in the transpiler pass :class:`~.ElidePermutations` where the + qubit mapping was not updated correctly in the presence of :class:`.PermutationGate`\s. diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py index c96b5ca32d89..b2012d9e09b4 100644 --- a/test/python/transpiler/test_elide_permutations.py +++ b/test/python/transpiler/test_elide_permutations.py @@ -207,6 +207,23 @@ def test_permutation_in_middle(self): res = self.swap_pass(qc) self.assertEqual(res, expected) + def test_permutation_in_middle_another(self): + """Another example with permutation in the middle of the circuit.""" + qc = QuantumCircuit(5) + qc.cx(0, 1) + qc.append(PermutationGate([1, 2, 0]), [0, 2, 4]) + qc.cx(2, 3) + + # The permutation maps 2 -> 0, 4 -> 2, 0 -> 4, and 1 -> 1, 3 -> 3, + # corresponding to the qubit mapping [2, 1, 4, 3, 0]. + # so cx(2, 3) should become cx(4, 3) + expected = QuantumCircuit(5) + expected.cx(0, 1) + expected.cx(4, 3) + + res = self.swap_pass(qc) + self.assertEqual(res, expected) + def test_permutation_at_beginning(self): """Test permutation in beginning of bell is elided.""" qc = QuantumCircuit(3, 3) From 2b667b6f8fa5ef70f1bb632d3e6bc612892ecdae Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 5 Sep 2024 16:43:04 +0300 Subject: [PATCH 02/11] Rust docstring improvements --- crates/accelerate/src/elide_permutations.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 1266a33829ab..e8d5d5b44bea 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -21,7 +21,9 @@ use qiskit_circuit::Qubit; /// Args: /// dag (DAGCircuit): the DAG to be optimized. /// Returns: -/// DAGCircuit: the optimized DAG. +/// An `Option`: the value of `None` indicates that no optimization was +/// performed and the original `dag` should be used, otherwise it's a +/// tuple consisting of the optimized DAG and the induced qubit permutation. #[pyfunction] fn run(py: Python, dag: &mut DAGCircuit) -> PyResult)>> { let permutation_gate_names = ["swap".to_string(), "permutation".to_string()]; @@ -90,7 +92,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult Date: Sun, 8 Sep 2024 14:32:35 +0300 Subject: [PATCH 03/11] lint --- crates/pyext/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index eb7721461d8d..c660aa5ffd69 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -15,11 +15,11 @@ use pyo3::prelude::*; use qiskit_accelerate::{ check_map::check_map_mod, circuit_library::circuit_library, commutation_analysis::commutation_analysis, commutation_checker::commutation_checker, - convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, elide_permutations::elide_permutations, - error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, - filter_op_nodes::filter_op_nodes_mod, inverse_cancellation::inverse_cancellation_mod, - isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, + convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, + elide_permutations::elide_permutations, error_map::error_map, + euler_one_qubit_decomposer::euler_one_qubit_decomposer, filter_op_nodes::filter_op_nodes_mod, + inverse_cancellation::inverse_cancellation_mod, isometry::isometry, nlayout::nlayout, + optimize_1q_gates::optimize_1q_gates, 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, From 90ef328e748b83633eda02e8e67039ccf9d50020 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 8 Sep 2024 14:51:46 +0300 Subject: [PATCH 04/11] restoring elided comment --- crates/circuit/src/dag_circuit.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 81fed40827b6..3c16b1b53307 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -6339,6 +6339,11 @@ impl DAGCircuit { } } + /// Return the op name counts in the circuit + /// + /// Args: + /// py: The python token necessary for control flow recursion + /// recurse: Whether to recurse into control flow ops or not pub fn count_ops( &self, py: Python, From 004446c8966508dbbf0f7d33290d1ccff3a6b0b4 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 8 Sep 2024 14:53:44 +0300 Subject: [PATCH 05/11] explicitlt setting dtype=int for permutation gates --- qiskit/circuit/library/generalized_gates/permutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py index b2d17d2bed23..76f4ab7e04f9 100644 --- a/qiskit/circuit/library/generalized_gates/permutation.py +++ b/qiskit/circuit/library/generalized_gates/permutation.py @@ -141,7 +141,7 @@ def __init__( raise CircuitError( "Permutation pattern must be some ordering of 0..num_qubits-1 in a list." ) - pattern = np.array(pattern) + pattern = np.array(pattern, dtype=int) super().__init__(name="permutation", num_qubits=num_qubits, params=[pattern]) From baf6213147de78aff6ed5b5828026b26f91a8b99 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 8 Sep 2024 15:13:56 +0300 Subject: [PATCH 06/11] another attempt --- qiskit/circuit/library/generalized_gates/permutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py index 76f4ab7e04f9..285590962c8d 100644 --- a/qiskit/circuit/library/generalized_gates/permutation.py +++ b/qiskit/circuit/library/generalized_gates/permutation.py @@ -141,7 +141,7 @@ def __init__( raise CircuitError( "Permutation pattern must be some ordering of 0..num_qubits-1 in a list." ) - pattern = np.array(pattern, dtype=int) + pattern = np.array(pattern, dtype=np.int32) super().__init__(name="permutation", num_qubits=num_qubits, params=[pattern]) From a20d0779550f35622d15d9da2547a96672db7c09 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 8 Sep 2024 16:25:25 +0300 Subject: [PATCH 07/11] fmt after merge --- crates/pyext/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index d423baf0ee98..1806400b2ac1 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -16,11 +16,11 @@ use qiskit_accelerate::{ check_map::check_map_mod, circuit_library::circuit_library, commutation_analysis::commutation_analysis, commutation_checker::commutation_checker, convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, - elide_permutations::elide_permutations, - error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, - filter_op_nodes::filter_op_nodes_mod, gate_direction::gate_direction, - inverse_cancellation::inverse_cancellation_mod, isometry::isometry, nlayout::nlayout, - optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, + elide_permutations::elide_permutations, error_map::error_map, + euler_one_qubit_decomposer::euler_one_qubit_decomposer, filter_op_nodes::filter_op_nodes_mod, + gate_direction::gate_direction, inverse_cancellation::inverse_cancellation_mod, + isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, + 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, From 7227e7394072d4b0a478e8aae9f7556000858f86 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 19 Sep 2024 14:02:08 +0300 Subject: [PATCH 08/11] update after merge + comment from code review --- crates/accelerate/src/elide_permutations.rs | 7 ++----- .../port-elide-permutations-ed91c3d9cef2fec6.yaml | 4 ---- test/python/transpiler/test_elide_permutations.py | 14 +++++++++----- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index e8d5d5b44bea..834d933d882b 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -39,16 +39,13 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { let qargs = dag.get_qargs(inst.qubits); let index0 = qargs[0].0 as usize; let index1 = qargs[1].0 as usize; - let prev0 = mapping[index0]; - let prev1 = mapping[index1]; - mapping[index0] = prev1; - mapping[index1] = prev0; + mapping.swap(index0, index1); } ("permutation", None) => { if let Param::Obj(ref pyobj) = inst.params.as_ref().unwrap()[0] { diff --git a/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml b/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml index 3620520ca9f6..705e82f0fb6e 100644 --- a/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml +++ b/releasenotes/notes/port-elide-permutations-ed91c3d9cef2fec6.yaml @@ -3,7 +3,3 @@ features_transpiler: - | Port most of the logic of the transpiler pass :class:`~.ElidePermutations` to Rust. -fixes: - - | - Fixed a bug in the transpiler pass :class:`~.ElidePermutations` where the - qubit mapping was not updated correctly in the presence of :class:`.PermutationGate`\s. diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py index b2012d9e09b4..59b245b83d75 100644 --- a/test/python/transpiler/test_elide_permutations.py +++ b/test/python/transpiler/test_elide_permutations.py @@ -207,16 +207,20 @@ def test_permutation_in_middle(self): res = self.swap_pass(qc) self.assertEqual(res, expected) - def test_permutation_in_middle_another(self): - """Another example with permutation in the middle of the circuit.""" + def test_partial_permutation_in_middle(self): + """Test with a permutation gate in the middle of a circuit, + with the permutation gate defined only on a subset of qubits. + """ qc = QuantumCircuit(5) qc.cx(0, 1) qc.append(PermutationGate([1, 2, 0]), [0, 2, 4]) qc.cx(2, 3) - # The permutation maps 2 -> 0, 4 -> 2, 0 -> 4, and 1 -> 1, 3 -> 3, - # corresponding to the qubit mapping [2, 1, 4, 3, 0]. - # so cx(2, 3) should become cx(4, 3) + # The permutation corresponding to the permutation gate maps + # 2 -> 0, 4 -> 2, 0 -> 4, and 1 -> 1, 3 -> 3. + # Instead of the permutation gate, we can relabel the qubits + # 0 -> 2, 1 -> 1, 2 -> 4, 3 -> 3, 4 -> 2. + # Hence cx(2, 3) becomes cx(4, 3). expected = QuantumCircuit(5) expected.cx(0, 1) expected.cx(4, 3) From 8e0198f72efe3b2af92b383cd837f7300ac26a71 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 19 Sep 2024 15:24:13 +0300 Subject: [PATCH 09/11] switching to apply_operation_back --- crates/accelerate/src/elide_permutations.rs | 16 ++++++++++++---- crates/circuit/src/dag_circuit.rs | 12 +----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 834d933d882b..cd65f9fd830d 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -75,17 +75,25 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { // General instruction - let mut mapped_inst = inst.clone(); let qargs = dag.get_qargs(inst.qubits); + let cargs = dag.get_cargs(inst.clbits); let mapped_qargs: Vec = qargs .iter() .map(|q| q.0 as usize) .map(|q| mapping[q]) .map(|q| Qubit(q.try_into().unwrap())) .collect(); - let mapped_qubits = new_dag.set_qargs(&mapped_qargs); - mapped_inst.qubits = mapped_qubits; - new_dag.push_back(py, mapped_inst)?; + let params = inst.params.clone(); + + new_dag.apply_operation_back( + py, + inst.op.clone(), + &mapped_qargs, + cargs, + None, //params, + inst.extra_attrs.clone(), + None, + )?; } } } else { diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 9f97212cfbc7..0d357b31ad96 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5074,7 +5074,7 @@ impl DAGCircuit { /// This is mostly used to apply operations from one DAG to /// another that was created from the first via /// [DAGCircuit::copy_empty_like]. - pub fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { + fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { let op_name = instr.op.name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &instr) { @@ -6299,16 +6299,6 @@ impl DAGCircuit { self.cargs_interner.get(index) } - /// Insert qargs, return the intern index - pub fn set_qargs(&mut self, qargs: &[Qubit]) -> Interned<[Qubit]> { - self.qargs_interner.insert(qargs) - } - - /// Insert cargs, return the intern index - pub fn set_cargs(&mut self, cargs: &[Clbit]) -> Interned<[Clbit]> { - self.cargs_interner.insert(cargs) - } - /// Insert a new 1q standard gate on incoming qubit pub fn insert_1q_on_incoming_qubit( &mut self, From 05870e6c2ab531e700907b33552b4f52d79dd0e5 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 25 Sep 2024 12:05:58 +0300 Subject: [PATCH 10/11] Comments from code review --- crates/accelerate/src/elide_permutations.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index cd65f9fd830d..bf7d2faa5721 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -90,8 +90,9 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult Date: Tue, 1 Oct 2024 14:13:22 +0300 Subject: [PATCH 11/11] fix using params --- crates/accelerate/src/elide_permutations.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index bf7d2faa5721..7d6326d5c496 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -83,14 +83,13 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult