diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs new file mode 100644 index 000000000000..8ebf50cd372d --- /dev/null +++ b/crates/accelerate/src/check_map.rs @@ -0,0 +1,98 @@ +// 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::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 [Qubit]>, +) -> PyResult> { + let check_qubits = |qubits: &[Qubit]| -> bool { + match wire_map { + Some(wire_map) => { + 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()]), + } + }; + 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"))? + .downcast::()? + .borrow(); + let new_dag: DAGCircuit = + circuit_to_dag.call1((block_obj.clone(),))?.extract()?; + let wire_map = (0..block.num_qubits()) + .map(|inner| { + let outer = qubits[inner]; + match wire_map { + Some(wire_map) => wire_map[outer.0 as usize], + None => 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 3ddab8727871..26d93ab3f65e 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 commutation_analysis; pub mod commutation_checker; diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index defc4bf3e9b5..331703809397 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -13,12 +13,13 @@ 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, - 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, + 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, + 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, @@ -43,6 +44,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 4ac192176b51..27830243d852 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -93,6 +93,7 @@ sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase sys.modules["qiskit._accelerate.inverse_cancellation"] = _accelerate.inverse_cancellation +sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map sys.modules["qiskit._accelerate.filter_op_nodes"] = _accelerate.filter_op_nodes 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" + )