From 5ecf22e6b3714ffcf9c0082bf522b3e44ce8f8fd Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Sun, 25 Aug 2024 13:29:34 -0400 Subject: [PATCH 1/4] Fully port gates_in_basis to Rust. --- crates/accelerate/src/gates_in_basis.rs | 121 ++++++++++++++++++ crates/accelerate/src/lib.rs | 1 + .../accelerate/src/target_transpiler/mod.rs | 6 +- .../target_transpiler/nullable_index_map.rs | 2 +- crates/circuit/src/dag_circuit.rs | 9 ++ crates/circuit/src/lib.rs | 2 +- crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/transpiler/passes/utils/gates_basis.py | 37 +----- 9 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 crates/accelerate/src/gates_in_basis.rs diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs new file mode 100644 index 000000000000..95a44e3abf28 --- /dev/null +++ b/crates/accelerate/src/gates_in_basis.rs @@ -0,0 +1,121 @@ +// 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] +pub fn any_gate_missing_from_target( + _py: Python, + 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] +pub 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 9111f932e270..9d37f954e9fe 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -26,6 +26,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/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 65fc8e80d750..3f7029a7287d 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -50,9 +50,9 @@ mod exceptions { } // Custom types +pub type PropsMap = NullableIndexMap>; pub type Qargs = SmallVec<[PhysicalQubit; 2]>; type GateMap = IndexMap; -type PropsMap = NullableIndexMap>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; /// Represents a Qiskit `Gate` object or a Variadic instruction. @@ -102,7 +102,7 @@ impl TargetOperation { /// Represents a Qiskit `Gate` object, keeps a reference to its Python /// instance for caching purposes. #[derive(Debug, Clone)] -pub(crate) struct NormalOperation { +pub struct NormalOperation { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, op_object: PyObject, @@ -150,7 +150,7 @@ memory. module = "qiskit._accelerate.target" )] #[derive(Clone, Debug)] -pub(crate) struct Target { +pub struct Target { #[pyo3(get, set)] pub description: Option, #[pyo3(get)] diff --git a/crates/accelerate/src/target_transpiler/nullable_index_map.rs b/crates/accelerate/src/target_transpiler/nullable_index_map.rs index e6e2a0fca3a3..6f2cf88b7dca 100644 --- a/crates/accelerate/src/target_transpiler/nullable_index_map.rs +++ b/crates/accelerate/src/target_transpiler/nullable_index_map.rs @@ -37,7 +37,7 @@ type BaseMap = IndexMap; /// **Warning:** This is an experimental feature and should be used with care as it does not /// fully implement all the methods present in `IndexMap` due to API limitations. #[derive(Debug, Clone)] -pub(crate) struct NullableIndexMap +pub struct NullableIndexMap where K: Eq + Hash + Clone, V: Clone, diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 54f270b30558..47d9447df214 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -81,6 +81,15 @@ pub enum NodeType { Operation(PackedInstruction), } +impl NodeType { + 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 675cdfb3961d..4e5e61b15287 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -39,6 +39,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 25137d7a5918..340b0267c3ba 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -57,6 +57,7 @@ sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout 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 From cd7c7642f9ef152a38655cbf6d909840fa8e2703 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 26 Sep 2024 14:28:16 -0400 Subject: [PATCH 2/4] Revert visibility to no longer public. --- crates/accelerate/src/gates_in_basis.rs | 8 ++------ crates/accelerate/src/target_transpiler/mod.rs | 6 +++--- .../src/target_transpiler/nullable_index_map.rs | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 95a44e3abf28..36d79a65b030 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -23,11 +23,7 @@ use qiskit_circuit::packed_instruction::PackedInstruction; use qiskit_circuit::Qubit; #[pyfunction] -pub fn any_gate_missing_from_target( - _py: Python, - dag: &DAGCircuit, - target: &Target, -) -> PyResult { +fn any_gate_missing_from_target(_py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { #[inline] fn is_universal(gate: &PackedInstruction) -> bool { matches!(gate.op.name(), "barrier" | "store") @@ -101,7 +97,7 @@ pub fn any_gate_missing_from_target( } #[pyfunction] -pub fn any_gate_missing_from_basis( +fn any_gate_missing_from_basis( py: Python, dag: &DAGCircuit, basis: HashSet, diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 3f7029a7287d..65fc8e80d750 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -50,9 +50,9 @@ mod exceptions { } // Custom types -pub type PropsMap = NullableIndexMap>; pub type Qargs = SmallVec<[PhysicalQubit; 2]>; type GateMap = IndexMap; +type PropsMap = NullableIndexMap>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; /// Represents a Qiskit `Gate` object or a Variadic instruction. @@ -102,7 +102,7 @@ impl TargetOperation { /// Represents a Qiskit `Gate` object, keeps a reference to its Python /// instance for caching purposes. #[derive(Debug, Clone)] -pub struct NormalOperation { +pub(crate) struct NormalOperation { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, op_object: PyObject, @@ -150,7 +150,7 @@ memory. module = "qiskit._accelerate.target" )] #[derive(Clone, Debug)] -pub struct Target { +pub(crate) struct Target { #[pyo3(get, set)] pub description: Option, #[pyo3(get)] diff --git a/crates/accelerate/src/target_transpiler/nullable_index_map.rs b/crates/accelerate/src/target_transpiler/nullable_index_map.rs index 6f2cf88b7dca..e6e2a0fca3a3 100644 --- a/crates/accelerate/src/target_transpiler/nullable_index_map.rs +++ b/crates/accelerate/src/target_transpiler/nullable_index_map.rs @@ -37,7 +37,7 @@ type BaseMap = IndexMap; /// **Warning:** This is an experimental feature and should be used with care as it does not /// fully implement all the methods present in `IndexMap` due to API limitations. #[derive(Debug, Clone)] -pub struct NullableIndexMap +pub(crate) struct NullableIndexMap where K: Eq + Hash + Clone, V: Clone, From 6245eeb344e491211fbf29d51b0c8defe60df0f6 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 26 Sep 2024 14:28:37 -0400 Subject: [PATCH 3/4] Add docstring for unwrap_operation. --- crates/circuit/src/dag_circuit.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 47d9447df214..8970e65119c0 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -82,6 +82,10 @@ pub enum NodeType { } 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, From e62a90765d892b65aeb26139ea9612202bc8925f Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 26 Sep 2024 15:04:47 -0400 Subject: [PATCH 4/4] Remove unused py token. --- crates/accelerate/src/gates_in_basis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 36d79a65b030..69f656c724c9 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -23,7 +23,7 @@ use qiskit_circuit::packed_instruction::PackedInstruction; use qiskit_circuit::Qubit; #[pyfunction] -fn any_gate_missing_from_target(_py: Python, dag: &DAGCircuit, target: &Target) -> PyResult { +fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult { #[inline] fn is_universal(gate: &PackedInstruction) -> bool { matches!(gate.op.name(), "barrier" | "store")