diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs new file mode 100644 index 000000000000..69f656c724c9 --- /dev/null +++ b/crates/accelerate/src/gates_in_basis.rs @@ -0,0 +1,117 @@ +// 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::prelude::*; +use qiskit_circuit::circuit_data::CircuitData; +use smallvec::SmallVec; + +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::Target; +use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::operations::Operation; +use qiskit_circuit::packed_instruction::PackedInstruction; +use qiskit_circuit::Qubit; + +#[pyfunction] +fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult { + #[inline] + fn is_universal(gate: &PackedInstruction) -> bool { + matches!(gate.op.name(), "barrier" | "store") + } + + fn visit_gate( + target: &Target, + gate: &PackedInstruction, + qargs: &[Qubit], + wire_map: &HashMap, + ) -> PyResult { + let qargs_mapped = SmallVec::from_iter(qargs.iter().map(|q| wire_map[q])); + if !target.instruction_supported(gate.op.name(), Some(&qargs_mapped)) { + return Ok(true); + } + + if gate.op.control_flow() { + for block in gate.op.blocks() { + let block_qubits = (0..block.num_qubits()).map(|i| Qubit(i.try_into().unwrap())); + let inner_wire_map = qargs + .iter() + .zip(block_qubits) + .map(|(outer, inner)| (inner, wire_map[outer])) + .collect(); + if visit_circuit(target, &block, &inner_wire_map)? { + return Ok(true); + } + } + } + Ok(false) + } + + fn visit_circuit( + target: &Target, + circuit: &CircuitData, + wire_map: &HashMap, + ) -> PyResult { + for gate in circuit.iter() { + if is_universal(gate) { + continue; + } + let qargs = circuit.qargs_interner().get(gate.qubits); + if visit_gate(target, gate, qargs, wire_map)? { + return Ok(true); + } + } + Ok(false) + } + + // In the outer DAG, virtual and physical bits are the same thing. + let wire_map: HashMap = + HashMap::from_iter((0..dag.num_qubits()).map(|i| { + ( + Qubit(i.try_into().unwrap()), + PhysicalQubit::new(i.try_into().unwrap()), + ) + })); + + // Process the DAG. + for gate in dag.op_nodes(true) { + let gate = dag.dag()[gate].unwrap_operation(); + if is_universal(gate) { + continue; + } + let qargs = dag.qargs_interner().get(gate.qubits); + if visit_gate(target, gate, qargs, &wire_map)? { + return Ok(true); + } + } + Ok(false) +} + +#[pyfunction] +fn any_gate_missing_from_basis( + py: Python, + dag: &DAGCircuit, + basis: HashSet, +) -> PyResult { + for (gate, _) in dag.count_ops(py, true)? { + if !basis.contains(gate.as_str()) { + return Ok(true); + } + } + Ok(false) +} + +pub fn gates_in_basis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(any_gate_missing_from_target))?; + m.add_wrapped(wrap_pyfunction!(any_gate_missing_from_basis))?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index a0a2bfcdcaad..aa4776082987 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -28,6 +28,7 @@ pub mod error_map; pub mod euler_one_qubit_decomposer; pub mod filter_op_nodes; pub mod gate_direction; +pub mod gates_in_basis; pub mod inverse_cancellation; pub mod isometry; pub mod nlayout; diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 31cc67d25e01..dbe8883791f0 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -81,6 +81,19 @@ pub enum NodeType { Operation(PackedInstruction), } +impl NodeType { + /// Unwraps this node as an operation and returns a reference to + /// the contained [PackedInstruction]. + /// + /// Panics if this is not an operation node. + pub fn unwrap_operation(&self) -> &PackedInstruction { + match self { + NodeType::Operation(instr) => instr, + _ => panic!("Node is not an operation!"), + } + } +} + #[derive(Clone, Debug)] pub enum Wire { Qubit(Qubit), diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index dcff558ade64..12351dcbbc70 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -20,7 +20,7 @@ mod dot_utils; mod error; pub mod gate_matrix; pub mod imports; -mod interner; +pub mod interner; pub mod operations; pub mod packed_instruction; pub mod parameter_table; diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index dfda80623d6f..458d9cc2bde8 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -41,6 +41,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::euler_one_qubit_decomposer::euler_one_qubit_decomposer, "euler_one_qubit_decomposer")?; add_submodule(m, ::qiskit_accelerate::filter_op_nodes::filter_op_nodes_mod, "filter_op_nodes")?; add_submodule(m, ::qiskit_accelerate::gate_direction::gate_direction, "gate_direction")?; + add_submodule(m, ::qiskit_accelerate::gates_in_basis::gates_in_basis, "gates_in_basis")?; add_submodule(m, ::qiskit_accelerate::inverse_cancellation::inverse_cancellation_mod, "inverse_cancellation")?; add_submodule(m, ::qiskit_accelerate::isometry::isometry, "isometry")?; add_submodule(m, ::qiskit_accelerate::nlayout::nlayout, "nlayout")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index e96045e6801f..359095f7a2eb 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -58,6 +58,7 @@ sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout sys.modules["qiskit._accelerate.equivalence"] = _accelerate.equivalence sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map +sys.modules["qiskit._accelerate.gates_in_basis"] = _accelerate.gates_in_basis sys.modules["qiskit._accelerate.isometry"] = _accelerate.isometry sys.modules["qiskit._accelerate.uc_gate"] = _accelerate.uc_gate sys.modules["qiskit._accelerate.euler_one_qubit_decomposer"] = ( diff --git a/qiskit/transpiler/passes/utils/gates_basis.py b/qiskit/transpiler/passes/utils/gates_basis.py index 16a68c3e533e..56a80c1f2288 100644 --- a/qiskit/transpiler/passes/utils/gates_basis.py +++ b/qiskit/transpiler/passes/utils/gates_basis.py @@ -12,9 +12,13 @@ """Check if all gates in the DAGCircuit are in the specified basis gates.""" -from qiskit.converters import circuit_to_dag from qiskit.transpiler.basepasses import AnalysisPass +from qiskit._accelerate.gates_in_basis import ( + any_gate_missing_from_basis, + any_gate_missing_from_target, +) + class GatesInBasis(AnalysisPass): """Check if all gates in a DAG are in a given set of gates""" @@ -40,35 +44,8 @@ def run(self, dag): if self._basis_gates is None and self._target is None: self.property_set["all_gates_in_basis"] = True return - gates_out_of_basis = False if self._target is not None: - - def _visit_target(dag, wire_map): - for gate in dag.op_nodes(): - # Barrier and store are assumed universal and supported by all backends - if gate.name in ("barrier", "store"): - continue - if not self._target.instruction_supported( - gate.name, tuple(wire_map[bit] for bit in gate.qargs) - ): - return True - # Control-flow ops still need to be supported, so don't skip them in the - # previous checks. - if gate.is_control_flow(): - for block in gate.op.blocks: - inner_wire_map = { - inner: wire_map[outer] - for outer, inner in zip(gate.qargs, block.qubits) - } - if _visit_target(circuit_to_dag(block), inner_wire_map): - return True - return False - - qubit_map = {qubit: index for index, qubit in enumerate(dag.qubits)} - gates_out_of_basis = _visit_target(dag, qubit_map) + gates_out_of_basis = any_gate_missing_from_target(dag, self._target) else: - for gate in dag.count_ops(recurse=True): - if gate not in self._basis_gates: - gates_out_of_basis = True - break + gates_out_of_basis = any_gate_missing_from_basis(dag, self._basis_gates) self.property_set["all_gates_in_basis"] = not gates_out_of_basis