From 88b4265159c86da131babd69c8996ae6b72a7fcd Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Sat, 24 Aug 2024 09:12:59 -0400 Subject: [PATCH 1/3] Fully port CheckMap to Rust This commit migrates the entirety of the CheckMap analysis pass to Rust. The pass operates solely in the rust domain and returns an `Option<(String, [u32; 2])>` to Python which is used to set the two property set fields appropriately. All the analysis of the dag is done in Rust. There is still Python interaction required though because control flow operations are only defined in Python. However the interaction is minimal and only to get the circuits for control flow blocks and converting them into DAGs (at least until #13001 is complete). This commit is based on top of #12959 and will need to be rebased after that merges. Closes #12251 Part of #12208 --- crates/accelerate/src/check_map.rs | 93 +++++++++++++++++++++ crates/accelerate/src/lib.rs | 1 + crates/pyext/src/lib.rs | 18 ++-- qiskit/__init__.py | 1 + qiskit/transpiler/passes/utils/check_map.py | 33 +++----- 5 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 crates/accelerate/src/check_map.rs diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs new file mode 100644 index 000000000000..2721ec209e5f --- /dev/null +++ b/crates/accelerate/src/check_map.rs @@ -0,0 +1,93 @@ +// 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 hashbrown::{HashMap, HashSet}; +use pyo3::intern; +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::imports::CIRCUIT_TO_DAG; +use qiskit_circuit::operations::{Operation, OperationRef}; +use qiskit_circuit::Qubit; + +fn recurse<'py>( + py: Python<'py>, + dag: &'py DAGCircuit, + edge_set: &'py HashSet<[u32; 2]>, + wire_map: Option<&'py HashMap>, +) -> PyResult> { + let check_qubits = |qubits: &[Qubit]| -> bool { + match wire_map { + Some(wire_map) => { + let mapped_bits = [wire_map[&qubits[0]], wire_map[&qubits[1]]]; + edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()]) + } + None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), + } + }; + for node in dag.op_nodes(false) { + if let NodeType::Operation(inst) = &dag.dag[node] { + let qubits = dag.get_qargs(inst.qubits); + if inst.op.control_flow() { + if let OperationRef::Instruction(py_inst) = inst.op.view() { + let raw_blocks = py_inst.instruction.getattr(py, "blocks")?; + let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py); + for raw_block in raw_blocks.bind(py).iter().unwrap() { + let block_obj = raw_block?; + let block = block_obj + .getattr(intern!(py, "_data"))? + .extract::()?; + let new_dag: DAGCircuit = + circuit_to_dag.call1((block_obj.clone(),))?.extract()?; + let wire_map = (0..block.num_qubits()) + .map(|x| Qubit(x as u32)) + .zip(qubits) + .map(|(inner, outer)| match wire_map { + Some(wire_map) => (inner, wire_map[outer]), + None => (inner, *outer), + }) + .collect(); + let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?; + if res.is_some() { + return Ok(res); + } + } + } + } else if qubits.len() == 2 + && (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?) + && !check_qubits(qubits) + { + return Ok(Some(( + inst.op.name().to_string(), + [qubits[0].0, qubits[1].0], + ))); + } + } + } + Ok(None) +} + +#[pyfunction] +pub fn check_map( + py: Python, + dag: &DAGCircuit, + edge_set: HashSet<[u32; 2]>, +) -> PyResult> { + recurse(py, dag, &edge_set, None) +} + +pub fn check_map_mod(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(check_map))?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 5414183d22cc..80e59536e1ea 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -14,6 +14,7 @@ use std::env; use pyo3::import_exception; +pub mod check_map; pub mod circuit_library; pub mod convert_2q_block_matrix; pub mod dense_layout; diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index e8971bc87629..3c22cd4b80bf 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -13,14 +13,15 @@ use pyo3::prelude::*; use qiskit_accelerate::{ - circuit_library::circuit_library, 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, + check_map::check_map_mod, circuit_library::circuit_library, + 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, }; #[inline(always)] @@ -40,6 +41,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, qiskit_qasm2::qasm2, "qasm2")?; add_submodule(m, qiskit_qasm3::qasm3, "qasm3")?; add_submodule(m, circuit_library, "circuit_library")?; + add_submodule(m, check_map_mod, "check_map")?; add_submodule(m, convert_2q_block_matrix, "convert_2q_block_matrix")?; add_submodule(m, dense_layout, "dense_layout")?; add_submodule(m, error_map, "error_map")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 33933fd8fd7e..0d1f26846a6e 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -87,6 +87,7 @@ sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase +sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index bd78c65de5f4..4048d93df22c 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -14,7 +14,8 @@ from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.target import Target -from qiskit.converters import circuit_to_dag + +from qiskit._accelerate import check_map class CheckMap(AnalysisPass): @@ -67,25 +68,11 @@ def run(self, dag): if not self.qargs: self.property_set[self.property_set_field] = True return - wire_map = {bit: index for index, bit in enumerate(dag.qubits)} - self.property_set[self.property_set_field] = self._recurse(dag, wire_map) - - def _recurse(self, dag, wire_map) -> bool: - for node in dag.op_nodes(include_directives=False): - if node.is_control_flow(): - for block in node.op.blocks: - inner_wire_map = { - inner: wire_map[outer] for inner, outer in zip(block.qubits, node.qargs) - } - if not self._recurse(circuit_to_dag(block), inner_wire_map): - return False - elif ( - len(node.qargs) == 2 - and not dag.has_calibration_for(node) - and (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) not in self.qargs - ): - self.property_set["check_map_msg"] = ( - f"{node.name}({wire_map[node.qargs[0]]}, {wire_map[node.qargs[1]]}) failed" - ) - return False - return True + res = check_map.check_map(dag, self.qargs) + if res is None: + self.property_set[self.property_set_field] = True + return + self.property_set[self.property_set_field] = False + self.property_set["check_map_msg"] = ( + f"{res[0]}({dag.qubits[res[1][0]]}, {dag.qubits[res[1][1]]}) failed" + ) From 341a65b9ade8ee4e8246584b551cb1fbe271bebc Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Sat, 24 Aug 2024 12:49:54 -0400 Subject: [PATCH 2/3] Use a Vec for wire_map instead of a HashMap This commit switches to using a Vec for the internal wire_map used to map control flow qubits. A HashMap was originally used because in Python a dictionary is used. However, in the rust domain the inner qubits are contiguous integers starting from 0 so a Vec can be used for better performance in the case we have control flow. --- crates/accelerate/src/check_map.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 2721ec209e5f..0a35883a5059 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use hashbrown::{HashMap, HashSet}; +use hashbrown::HashSet; use pyo3::intern; use pyo3::prelude::*; use pyo3::wrap_pyfunction; @@ -25,12 +25,15 @@ fn recurse<'py>( py: Python<'py>, dag: &'py DAGCircuit, edge_set: &'py HashSet<[u32; 2]>, - wire_map: Option<&'py HashMap>, + wire_map: Option<&'py [Qubit]>, ) -> PyResult> { let check_qubits = |qubits: &[Qubit]| -> bool { match wire_map { Some(wire_map) => { - let mapped_bits = [wire_map[&qubits[0]], wire_map[&qubits[1]]]; + let mapped_bits = [ + wire_map[qubits[0].0 as usize], + wire_map[qubits[1].0 as usize], + ]; edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()]) } None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), @@ -51,13 +54,14 @@ fn recurse<'py>( let new_dag: DAGCircuit = circuit_to_dag.call1((block_obj.clone(),))?.extract()?; let wire_map = (0..block.num_qubits()) - .map(|x| Qubit(x as u32)) - .zip(qubits) - .map(|(inner, outer)| match wire_map { - Some(wire_map) => (inner, wire_map[outer]), - None => (inner, *outer), + .map(|inner| { + let outer = qubits[inner]; + match wire_map { + Some(wire_map) => wire_map[outer.0 as usize], + None => outer, + } }) - .collect(); + .collect::>(); let res = recurse(py, &new_dag, edge_set, Some(&wire_map))?; if res.is_some() { return Ok(res); From 64c47b886e6c56c589595f5b72ca70af37640066 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 4 Sep 2024 12:57:24 -0400 Subject: [PATCH 3/3] Update crates/accelerate/src/check_map.rs Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/accelerate/src/check_map.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 0a35883a5059..8ebf50cd372d 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -50,7 +50,8 @@ fn recurse<'py>( let block_obj = raw_block?; let block = block_obj .getattr(intern!(py, "_data"))? - .extract::()?; + .downcast::()? + .borrow(); let new_dag: DAGCircuit = circuit_to_dag.call1((block_obj.clone(),))?.extract()?; let wire_map = (0..block.num_qubits())