diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs new file mode 100644 index 000000000000..e389151b6b04 --- /dev/null +++ b/crates/accelerate/src/equivalence.rs @@ -0,0 +1,805 @@ +// 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 itertools::Itertools; + +use pyo3::exceptions::PyTypeError; +use qiskit_circuit::parameter_table::ParameterUuid; +use rustworkx_core::petgraph::csr::IndexType; +use rustworkx_core::petgraph::stable_graph::StableDiGraph; +use rustworkx_core::petgraph::visit::IntoEdgeReferences; + +use smallvec::SmallVec; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::{error::Error, fmt::Display}; + +use exceptions::CircuitError; + +use ahash::RandomState; +use indexmap::{IndexMap, IndexSet}; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList, PySet, PyString}; + +use rustworkx_core::petgraph::{ + graph::{EdgeIndex, NodeIndex}, + visit::EdgeRef, +}; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::circuit_instruction::OperationFromPython; +use qiskit_circuit::imports::{ImportOnceCell, QUANTUM_CIRCUIT}; +use qiskit_circuit::operations::Param; +use qiskit_circuit::operations::{Operation, OperationRef}; +use qiskit_circuit::packed_instruction::PackedOperation; + +mod exceptions { + use pyo3::import_exception_bound; + import_exception_bound! {qiskit.circuit.exceptions, CircuitError} +} +pub static PYDIGRAPH: ImportOnceCell = ImportOnceCell::new("rustworkx", "PyDiGraph"); + +// Custom Structs + +#[pyclass(frozen, sequence, module = "qiskit._accelerate.equivalence")] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Key { + #[pyo3(get)] + pub name: String, + #[pyo3(get)] + pub num_qubits: u32, +} + +#[pymethods] +impl Key { + #[new] + #[pyo3(signature = (name, num_qubits))] + fn new(name: String, num_qubits: u32) -> Self { + Self { name, num_qubits } + } + + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + (self.name.to_string(), self.num_qubits).hash(&mut hasher); + hasher.finish() + } + + fn __repr__(slf: PyRef) -> String { + slf.to_string() + } + + fn __getnewargs__(slf: PyRef) -> (Bound, u32) { + ( + PyString::new_bound(slf.py(), slf.name.as_str()), + slf.num_qubits, + ) + } + + // Ord methods for Python + fn __lt__(&self, other: &Self) -> bool { + self.lt(other) + } + fn __le__(&self, other: &Self) -> bool { + self.le(other) + } + fn __eq__(&self, other: &Self) -> bool { + self.eq(other) + } + fn __ne__(&self, other: &Self) -> bool { + self.ne(other) + } + fn __ge__(&self, other: &Self) -> bool { + self.ge(other) + } + fn __gt__(&self, other: &Self) -> bool { + self.gt(other) + } +} +impl Key { + fn from_operation(operation: &PackedOperation) -> Self { + let op_ref: OperationRef = operation.view(); + Key { + name: op_ref.name().to_string(), + num_qubits: op_ref.num_qubits(), + } + } +} + +impl Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Key(name=\'{}\', num_qubits={})", + self.name, self.num_qubits + ) + } +} + +#[pyclass(frozen, sequence, module = "qiskit._accelerate.equivalence")] +#[derive(Debug, Clone)] +pub struct Equivalence { + #[pyo3(get)] + pub params: SmallVec<[Param; 3]>, + #[pyo3(get)] + pub circuit: CircuitRep, +} + +#[pymethods] +impl Equivalence { + #[new] + #[pyo3(signature = (params, circuit))] + fn new(params: SmallVec<[Param; 3]>, circuit: CircuitRep) -> Self { + Self { circuit, params } + } + + fn __repr__(&self) -> String { + self.to_string() + } + + fn __eq__(slf: &Bound, other: &Bound) -> PyResult { + let other_params = other.getattr("params")?; + let other_circuit = other.getattr("circuit")?; + Ok(other_params.eq(&slf.getattr("params")?)? + && other_circuit.eq(&slf.getattr("circuit")?)?) + } + + fn __getnewargs__<'py>( + slf: &'py Bound<'py, Self>, + ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { + Ok((slf.getattr("params")?, slf.getattr("circuit")?)) + } +} + +impl Display for Equivalence { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Equivalence(params=[{}], circuit={:?})", + self.params + .iter() + .map(|param| format!("{:?}", param)) + .format(", "), + self.circuit + ) + } +} + +#[pyclass(frozen, sequence, module = "qiskit._accelerate.equivalence")] +#[derive(Debug, Clone)] +pub struct NodeData { + #[pyo3(get)] + pub key: Key, + #[pyo3(get)] + pub equivs: Vec, +} + +#[pymethods] +impl NodeData { + #[new] + #[pyo3(signature = (key, equivs))] + fn new(key: Key, equivs: Vec) -> Self { + Self { key, equivs } + } + + fn __repr__(&self) -> String { + self.to_string() + } + + fn __eq__(slf: &Bound, other: &Bound) -> PyResult { + Ok(slf.getattr("key")?.eq(other.getattr("key")?)? + && slf.getattr("equivs")?.eq(other.getattr("equivs")?)?) + } + + fn __getnewargs__<'py>( + slf: &'py Bound<'py, Self>, + ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { + Ok((slf.getattr("key")?, slf.getattr("equivs")?)) + } +} + +impl Display for NodeData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "NodeData(key={}, equivs=[{}])", + self.key, + self.equivs.iter().format(", ") + ) + } +} + +#[pyclass(frozen, sequence, module = "qiskit._accelerate.equivalence")] +#[derive(Debug, Clone)] +pub struct EdgeData { + #[pyo3(get)] + pub index: usize, + #[pyo3(get)] + pub num_gates: usize, + #[pyo3(get)] + pub rule: Equivalence, + #[pyo3(get)] + pub source: Key, +} + +#[pymethods] +impl EdgeData { + #[new] + #[pyo3(signature = (index, num_gates, rule, source))] + fn new(index: usize, num_gates: usize, rule: Equivalence, source: Key) -> Self { + Self { + index, + num_gates, + rule, + source, + } + } + + fn __repr__(&self) -> String { + self.to_string() + } + + fn __eq__(slf: &Bound, other: &Bound) -> PyResult { + let other_borrowed = other.borrow(); + let slf_borrowed = slf.borrow(); + Ok(slf_borrowed.index == other_borrowed.index + && slf_borrowed.num_gates == other_borrowed.num_gates + && slf_borrowed.source == other_borrowed.source + && other.getattr("rule")?.eq(slf.getattr("rule")?)?) + } + + fn __getnewargs__(slf: PyRef) -> (usize, usize, Equivalence, Key) { + ( + slf.index, + slf.num_gates, + slf.rule.clone(), + slf.source.clone(), + ) + } +} + +impl Display for EdgeData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "EdgeData(index={}, num_gates={}, rule={}, source={})", + self.index, self.num_gates, self.rule, self.source + ) + } +} + +/// Enum that helps extract the Operation and Parameters on a Gate. +/// It is highly derivative of `PackedOperation` while also tracking the specific +/// parameter objects. +#[derive(Debug, Clone)] +pub struct GateOper { + operation: PackedOperation, + params: SmallVec<[Param; 3]>, +} + +impl<'py> FromPyObject<'py> for GateOper { + fn extract(ob: &'py PyAny) -> PyResult { + let op_struct: OperationFromPython = ob.extract()?; + Ok(Self { + operation: op_struct.operation, + params: op_struct.params, + }) + } +} + +/// Representation of QuantumCircuit by using an instance of `CircuitData`.] +/// +/// TODO: Remove this implementation once the `EquivalenceLibrary` is no longer +/// called from Python, or once the API is able to seamlessly accept instances +/// of `CircuitData`. +#[derive(Debug, Clone)] +pub struct CircuitRep(pub CircuitData); + +impl FromPyObject<'_> for CircuitRep { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + if ob.is_instance(QUANTUM_CIRCUIT.get_bound(ob.py()))? { + let data: Bound = ob.getattr("_data")?; + let data_downcast: Bound = data.downcast_into()?; + let data_extract: CircuitData = data_downcast.extract()?; + Ok(Self(data_extract)) + } else { + Err(PyTypeError::new_err( + "Provided object was not an instance of QuantumCircuit", + )) + } + } +} + +impl IntoPy for CircuitRep { + fn into_py(self, py: Python<'_>) -> PyObject { + QUANTUM_CIRCUIT + .get_bound(py) + .call_method1("_from_circuit_data", (self.0,)) + .unwrap() + .unbind() + } +} + +impl ToPyObject for CircuitRep { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.clone().into_py(py) + } +} + +// Custom Types +type GraphType = StableDiGraph>; +type KTIType = IndexMap; + +#[pyclass( + subclass, + name = "BaseEquivalenceLibrary", + module = "qiskit._accelerate.equivalence" +)] +#[derive(Debug, Clone)] +pub struct EquivalenceLibrary { + graph: GraphType, + key_to_node_index: KTIType, + rule_id: usize, + _graph: Option, +} + +#[pymethods] +impl EquivalenceLibrary { + /// Create a new equivalence library. + /// + /// Args: + /// base (Optional[EquivalenceLibrary]): Base equivalence library to + /// be referenced if an entry is not found in this library. + #[new] + #[pyo3(signature= (base=None))] + fn new(base: Option<&EquivalenceLibrary>) -> Self { + if let Some(base) = base { + Self { + graph: base.graph.clone(), + key_to_node_index: base.key_to_node_index.clone(), + rule_id: base.rule_id, + _graph: None, + } + } else { + Self { + graph: GraphType::new(), + key_to_node_index: KTIType::default(), + rule_id: 0_usize, + _graph: None, + } + } + } + + /// Add a new equivalence to the library. Future queries for the Gate + /// will include the given circuit, in addition to all existing equivalences + /// (including those from base). + /// + /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their + /// `Gate.params`) can be marked equivalent to parameterized circuits, + /// provided the parameters match. + /// + /// Args: + /// gate (Gate): A Gate instance. + /// equivalent_circuit (QuantumCircuit): A circuit equivalently + /// implementing the given Gate. + #[pyo3(name = "add_equivalence")] + fn py_add_equivalence( + &mut self, + py: Python, + gate: GateOper, + equivalent_circuit: CircuitRep, + ) -> PyResult<()> { + self.add_equivalence(py, &gate.operation, &gate.params, equivalent_circuit) + } + + /// Check if a library contains any decompositions for gate. + /// + /// Args: + /// gate (Gate): A Gate instance. + /// + /// Returns: + /// Bool: True if gate has a known decomposition in the library. + /// False otherwise. + #[pyo3(name = "has_entry")] + fn py_has_entry(&self, gate: GateOper) -> bool { + self.has_entry(&gate.operation) + } + + /// Set the equivalence record for a Gate. Future queries for the Gate + /// will return only the circuits provided. + /// + /// Parameterized Gates (those including `qiskit.circuit.Parameters` in their + /// `Gate.params`) can be marked equivalent to parameterized circuits, + /// provided the parameters match. + /// + /// Args: + /// gate (Gate): A Gate instance. + /// entry (List['QuantumCircuit']) : A list of QuantumCircuits, each + /// equivalently implementing the given Gate. + #[pyo3(name = "set_entry")] + fn py_set_entry(&mut self, py: Python, gate: GateOper, entry: Vec) -> PyResult<()> { + self.set_entry(py, &gate.operation, &gate.params, entry) + } + + /// Gets the set of QuantumCircuits circuits from the library which + /// equivalently implement the given Gate. + /// + /// Parameterized circuits will have their parameters replaced with the + /// corresponding entries from Gate.params. + /// + /// Args: + /// gate (Gate) - Gate: A Gate instance. + /// + /// Returns: + /// List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty, + /// library contains no known decompositions of Gate. + /// + /// Returned circuits will be ordered according to their insertion in + /// the library, from earliest to latest, from top to base. The + /// ordering of the StandardEquivalenceLibrary will not generally be + /// consistent across Qiskit versions. + fn get_entry(&self, py: Python, gate: GateOper) -> PyResult> { + let key = Key::from_operation(&gate.operation); + let query_params = gate.params; + + let bound_equivalencies = self + ._get_equivalences(&key) + .into_iter() + .filter_map(|equivalence| rebind_equiv(py, equivalence, &query_params).ok()); + let return_list = PyList::empty_bound(py); + for equiv in bound_equivalencies { + return_list.append(equiv)?; + } + Ok(return_list.unbind()) + } + + // TODO: Remove once BasisTranslator is in Rust. + #[getter] + fn get_graph(&mut self, py: Python) -> PyResult { + if let Some(graph) = &self._graph { + Ok(graph.clone_ref(py)) + } else { + self._graph = Some(to_pygraph(py, &self.graph)?); + Ok(self + ._graph + .as_ref() + .map(|graph| graph.clone_ref(py)) + .unwrap()) + } + } + + /// Get all the equivalences for the given key + pub fn _get_equivalences(&self, key: &Key) -> Vec { + if let Some(key_in) = self.key_to_node_index.get(key) { + self.graph[*key_in].equivs.clone() + } else { + vec![] + } + } + + #[pyo3(name = "keys")] + fn py_keys(slf: PyRef) -> PyResult { + let py_dict = PyDict::new_bound(slf.py()); + for key in slf.keys() { + py_dict.set_item(key.clone().into_py(slf.py()), slf.py().None())?; + } + Ok(py_dict.as_any().call_method0("keys")?.into()) + } + + #[pyo3(name = "node_index")] + fn py_node_index(&self, key: &Key) -> usize { + self.node_index(key).index() + } + + fn __getstate__(slf: PyRef) -> PyResult> { + let ret = PyDict::new_bound(slf.py()); + ret.set_item("rule_id", slf.rule_id)?; + let key_to_usize_node: Bound = PyDict::new_bound(slf.py()); + for (key, val) in slf.key_to_node_index.iter() { + key_to_usize_node.set_item(key.clone().into_py(slf.py()), val.index())?; + } + ret.set_item("key_to_node_index", key_to_usize_node)?; + let graph_nodes: Bound = PyList::empty_bound(slf.py()); + for weight in slf.graph.node_weights() { + graph_nodes.append(weight.clone().into_py(slf.py()))?; + } + ret.set_item("graph_nodes", graph_nodes.unbind())?; + let edges = slf.graph.edge_references().map(|edge| { + ( + edge.source().index(), + edge.target().index(), + edge.weight().clone().into_py(slf.py()), + ) + }); + let graph_edges = PyList::empty_bound(slf.py()); + for edge in edges { + graph_edges.append(edge)?; + } + ret.set_item("graph_edges", graph_edges.unbind())?; + Ok(ret) + } + + fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { + slf.rule_id = state.get_item("rule_id")?.unwrap().extract()?; + let graph_nodes_ref: Bound = state.get_item("graph_nodes")?.unwrap(); + let graph_nodes: &Bound = graph_nodes_ref.downcast()?; + let graph_edge_ref: Bound = state.get_item("graph_edges")?.unwrap(); + let graph_edges: &Bound = graph_edge_ref.downcast()?; + slf.graph = GraphType::new(); + for node_weight in graph_nodes { + slf.graph.add_node(node_weight.extract()?); + } + for edge in graph_edges { + let (source_node, target_node, edge_weight) = edge.extract()?; + slf.graph.add_edge( + NodeIndex::new(source_node), + NodeIndex::new(target_node), + edge_weight, + ); + } + slf.key_to_node_index = state + .get_item("key_to_node_index")? + .unwrap() + .extract::>()? + .into_iter() + .map(|(key, val)| (key, NodeIndex::new(val))) + .collect(); + slf._graph = None; + Ok(()) + } +} + +// Rust native methods +impl EquivalenceLibrary { + /// Add a new equivalence to the library. Future queries for the Gate + /// will include the given circuit, in addition to all existing equivalences + /// (including those from base). + pub fn add_equivalence( + &mut self, + py: Python, + gate: &PackedOperation, + params: &[Param], + equivalent_circuit: CircuitRep, + ) -> PyResult<()> { + raise_if_shape_mismatch(gate, &equivalent_circuit)?; + raise_if_param_mismatch(py, params, equivalent_circuit.0.unsorted_parameters(py)?)?; + let key: Key = Key::from_operation(gate); + let equiv = Equivalence { + circuit: equivalent_circuit.clone(), + params: params.into(), + }; + + let target = self.set_default_node(key); + if let Some(node) = self.graph.node_weight_mut(target) { + node.equivs.push(equiv.clone()); + } + let sources: IndexSet = IndexSet::from_iter( + equivalent_circuit + .0 + .iter() + .map(|inst| Key::from_operation(&inst.op)), + ); + let edges = Vec::from_iter(sources.iter().map(|source| { + ( + self.set_default_node(source.clone()), + target, + EdgeData { + index: self.rule_id, + num_gates: sources.len(), + rule: equiv.clone(), + source: source.clone(), + }, + ) + })); + for edge in edges { + self.graph.add_edge(edge.0, edge.1, Some(edge.2)); + } + self.rule_id += 1; + self._graph = None; + Ok(()) + } + + /// Set the equivalence record for a Gate. Future queries for the Gate + /// will return only the circuits provided. + pub fn set_entry( + &mut self, + py: Python, + gate: &PackedOperation, + params: &[Param], + entry: Vec, + ) -> PyResult<()> { + for equiv in entry.iter() { + raise_if_shape_mismatch(gate, equiv)?; + raise_if_param_mismatch(py, params, equiv.0.unsorted_parameters(py)?)?; + } + let key = Key::from_operation(gate); + let node_index = self.set_default_node(key); + + if let Some(graph_ind) = self.graph.node_weight_mut(node_index) { + graph_ind.equivs.clear(); + } + + let edges: Vec = self + .graph + .edges_directed(node_index, rustworkx_core::petgraph::Direction::Incoming) + .map(|x| x.id()) + .collect(); + for edge in edges { + self.graph.remove_edge(edge); + } + for equiv in entry { + self.add_equivalence(py, gate, params, equiv)? + } + self._graph = None; + Ok(()) + } + + /// Rust native equivalent to `EquivalenceLibrary.has_entry()` + /// + /// Check if a library contains any decompositions for gate. + /// + /// # Arguments: + /// * `operation` OperationType: A Gate instance. + /// + /// # Returns: + /// `bool`: `true` if gate has a known decomposition in the library. + /// `false` otherwise. + pub fn has_entry(&self, operation: &PackedOperation) -> bool { + let key = Key::from_operation(operation); + self.key_to_node_index.contains_key(&key) + } + + pub fn keys(&self) -> impl Iterator { + self.key_to_node_index.keys() + } + + /// Create a new node if key not found + pub fn set_default_node(&mut self, key: Key) -> NodeIndex { + if let Some(value) = self.key_to_node_index.get(&key) { + *value + } else { + let node = self.graph.add_node(NodeData { + key: key.clone(), + equivs: vec![], + }); + self.key_to_node_index.insert(key, node); + node + } + } + + /// Retrieve the `NodeIndex` that represents a `Key` + /// + /// # Arguments: + /// * `key`: The `Key` to look for. + /// + /// # Returns: + /// `NodeIndex` + pub fn node_index(&self, key: &Key) -> NodeIndex { + self.key_to_node_index[key] + } + + /// Expose an immutable view of the inner graph. + pub fn graph(&self) -> &GraphType { + &self.graph + } +} + +fn raise_if_param_mismatch( + py: Python, + gate_params: &[Param], + circuit_parameters: Bound, +) -> PyResult<()> { + let gate_params_obj = PySet::new_bound( + py, + gate_params + .iter() + .filter(|param| matches!(param, Param::ParameterExpression(_))), + )?; + if !gate_params_obj.eq(&circuit_parameters)? { + return Err(CircuitError::new_err(format!( + "Cannot add equivalence between circuit and gate \ + of different parameters. Gate params: {:?}. \ + Circuit params: {:?}.", + gate_params, circuit_parameters + ))); + } + Ok(()) +} + +fn raise_if_shape_mismatch(gate: &PackedOperation, circuit: &CircuitRep) -> PyResult<()> { + let op_ref = gate.view(); + if op_ref.num_qubits() != circuit.0.num_qubits() as u32 + || op_ref.num_clbits() != circuit.0.num_clbits() as u32 + { + return Err(CircuitError::new_err(format!( + "Cannot add equivalence between circuit and gate \ + of different shapes. Gate: {} qubits and {} clbits. \ + Circuit: {} qubits and {} clbits.", + op_ref.num_qubits(), + op_ref.num_clbits(), + circuit.0.num_qubits(), + circuit.0.num_clbits() + ))); + } + Ok(()) +} + +fn rebind_equiv(py: Python, equiv: Equivalence, query_params: &[Param]) -> PyResult { + let (equiv_params, mut equiv_circuit) = (equiv.params, equiv.circuit); + let param_mapping: PyResult> = equiv_params + .iter() + .zip(query_params.iter()) + .filter_map(|(param_x, param_y)| match param_x { + Param::ParameterExpression(param) => { + let param_uuid = ParameterUuid::from_parameter(param.bind(py)); + Some(param_uuid.map(|uuid| (uuid, param_y))) + } + _ => None, + }) + .collect(); + equiv_circuit + .0 + .assign_parameters_from_mapping(py, param_mapping?)?; + Ok(equiv_circuit) +} + +// Errors + +#[derive(Debug, Clone)] +pub struct EquivalenceError { + pub message: String, +} + +impl EquivalenceError { + pub fn new_err(message: String) -> Self { + Self { message } + } +} + +impl Error for EquivalenceError {} + +impl Display for EquivalenceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +fn to_pygraph(py: Python, pet_graph: &StableDiGraph) -> PyResult +where + N: IntoPy + Clone, + E: IntoPy + Clone, +{ + let graph = PYDIGRAPH.get_bound(py).call0()?; + let node_weights: Vec = pet_graph.node_weights().cloned().collect(); + graph.call_method1("add_nodes_from", (node_weights,))?; + let edge_weights: Vec<(usize, usize, E)> = pet_graph + .edge_references() + .map(|edge| { + ( + edge.source().index(), + edge.target().index(), + edge.weight().clone(), + ) + }) + .collect(); + graph.call_method1("add_edges_from", (edge_weights,))?; + Ok(graph.unbind()) +} + +#[pymodule] +pub fn equivalence(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index c0a5fe8c03e1..e23ac39eccac 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -22,6 +22,7 @@ pub mod commutation_checker; pub mod convert_2q_block_matrix; pub mod dense_layout; pub mod edge_collections; +pub mod equivalence; pub mod error_map; pub mod euler_one_qubit_decomposer; pub mod filter_op_nodes; diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml index ed1f849bbf62..ef79525ab761 100644 --- a/crates/circuit/Cargo.toml +++ b/crates/circuit/Cargo.toml @@ -38,4 +38,4 @@ workspace = true features = ["union"] [features] -cache_pygates = [] +cache_pygates = [] \ No newline at end of file diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 56d527316b75..187dc8d6cc67 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -35,6 +35,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::commutation_checker::commutation_checker, "commutation_checker")?; add_submodule(m, ::qiskit_accelerate::convert_2q_block_matrix::convert_2q_block_matrix, "convert_2q_block_matrix")?; add_submodule(m, ::qiskit_accelerate::dense_layout::dense_layout, "dense_layout")?; + add_submodule(m, ::qiskit_accelerate::equivalence::equivalence, "equivalence")?; add_submodule(m, ::qiskit_accelerate::error_map::error_map, "error_map")?; 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")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 70c1ac3f431d..9480a44602d9 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -56,6 +56,7 @@ sys.modules["qiskit._accelerate.converters"] = _accelerate.converters 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.equivalence"] = _accelerate.equivalence sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map sys.modules["qiskit._accelerate.isometry"] = _accelerate.isometry sys.modules["qiskit._accelerate.uc_gate"] = _accelerate.uc_gate diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py index 17912517d244..05441e68e600 100644 --- a/qiskit/circuit/equivalence.py +++ b/qiskit/circuit/equivalence.py @@ -12,190 +12,24 @@ """Gate equivalence library.""" -import copy -from collections import namedtuple - from rustworkx.visualization import graphviz_draw import rustworkx as rx -from qiskit.exceptions import InvalidFileError -from .exceptions import CircuitError -from .parameter import Parameter -from .parameterexpression import ParameterExpression -Key = namedtuple("Key", ["name", "num_qubits"]) -Equivalence = namedtuple("Equivalence", ["params", "circuit"]) # Ordered to match Gate.params -NodeData = namedtuple("NodeData", ["key", "equivs"]) -EdgeData = namedtuple("EdgeData", ["index", "num_gates", "rule", "source"]) +from qiskit.exceptions import InvalidFileError +from qiskit._accelerate.equivalence import ( # pylint: disable=unused-import + BaseEquivalenceLibrary, + Key, + Equivalence, + NodeData, + EdgeData, +) -class EquivalenceLibrary: +class EquivalenceLibrary(BaseEquivalenceLibrary): """A library providing a one-way mapping of Gates to their equivalent implementations as QuantumCircuits.""" - def __init__(self, *, base=None): - """Create a new equivalence library. - - Args: - base (Optional[EquivalenceLibrary]): Base equivalence library to - be referenced if an entry is not found in this library. - """ - self._base = base - - if base is None: - self._graph = rx.PyDiGraph() - self._key_to_node_index = {} - # Some unique identifier for rules. - self._rule_id = 0 - else: - self._graph = base._graph.copy() - self._key_to_node_index = copy.deepcopy(base._key_to_node_index) - self._rule_id = base._rule_id - - @property - def graph(self) -> rx.PyDiGraph: - """Return graph representing the equivalence library data. - - This property should be treated as read-only as it provides - a reference to the internal state of the :class:`~.EquivalenceLibrary` object. - If the graph returned by this property is mutated it could corrupt the - the contents of the object. If you need to modify the output ``PyDiGraph`` - be sure to make a copy prior to any modification. - - Returns: - PyDiGraph: A graph object with equivalence data in each node. - """ - return self._graph - - def _set_default_node(self, key): - """Create a new node if key not found""" - if key not in self._key_to_node_index: - self._key_to_node_index[key] = self._graph.add_node(NodeData(key=key, equivs=[])) - return self._key_to_node_index[key] - - def add_equivalence(self, gate, equivalent_circuit): - """Add a new equivalence to the library. Future queries for the Gate - will include the given circuit, in addition to all existing equivalences - (including those from base). - - Parameterized Gates (those including `qiskit.circuit.Parameters` in their - `Gate.params`) can be marked equivalent to parameterized circuits, - provided the parameters match. - - Args: - gate (Gate): A Gate instance. - equivalent_circuit (QuantumCircuit): A circuit equivalently - implementing the given Gate. - """ - - _raise_if_shape_mismatch(gate, equivalent_circuit) - _raise_if_param_mismatch(gate.params, equivalent_circuit.parameters) - - key = Key(name=gate.name, num_qubits=gate.num_qubits) - equiv = Equivalence(params=gate.params.copy(), circuit=equivalent_circuit.copy()) - - target = self._set_default_node(key) - self._graph[target].equivs.append(equiv) - - sources = { - Key(name=instruction.operation.name, num_qubits=len(instruction.qubits)) - for instruction in equivalent_circuit - } - edges = [ - ( - self._set_default_node(source), - target, - EdgeData(index=self._rule_id, num_gates=len(sources), rule=equiv, source=source), - ) - for source in sources - ] - self._graph.add_edges_from(edges) - self._rule_id += 1 - - def has_entry(self, gate): - """Check if a library contains any decompositions for gate. - - Args: - gate (Gate): A Gate instance. - - Returns: - Bool: True if gate has a known decomposition in the library. - False otherwise. - """ - key = Key(name=gate.name, num_qubits=gate.num_qubits) - - return key in self._key_to_node_index - - def set_entry(self, gate, entry): - """Set the equivalence record for a Gate. Future queries for the Gate - will return only the circuits provided. - - Parameterized Gates (those including `qiskit.circuit.Parameters` in their - `Gate.params`) can be marked equivalent to parameterized circuits, - provided the parameters match. - - Args: - gate (Gate): A Gate instance. - entry (List['QuantumCircuit']) : A list of QuantumCircuits, each - equivalently implementing the given Gate. - """ - for equiv in entry: - _raise_if_shape_mismatch(gate, equiv) - _raise_if_param_mismatch(gate.params, equiv.parameters) - - node_index = self._set_default_node(Key(name=gate.name, num_qubits=gate.num_qubits)) - # Remove previous equivalences of this node, leaving in place any later equivalences that - # were added that use `gate`. - self._graph[node_index].equivs.clear() - for parent, child, _ in self._graph.in_edges(node_index): - # `child` should always be ourselves, but there might be parallel edges. - self._graph.remove_edge(parent, child) - for equivalence in entry: - self.add_equivalence(gate, equivalence) - - def get_entry(self, gate): - """Gets the set of QuantumCircuits circuits from the library which - equivalently implement the given Gate. - - Parameterized circuits will have their parameters replaced with the - corresponding entries from Gate.params. - - Args: - gate (Gate) - Gate: A Gate instance. - - Returns: - List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty, - library contains no known decompositions of Gate. - - Returned circuits will be ordered according to their insertion in - the library, from earliest to latest, from top to base. The - ordering of the StandardEquivalenceLibrary will not generally be - consistent across Qiskit versions. - """ - key = Key(name=gate.name, num_qubits=gate.num_qubits) - query_params = gate.params - - return [_rebind_equiv(equiv, query_params) for equiv in self._get_equivalences(key)] - - def keys(self): - """Return list of keys to key to node index map. - - Returns: - List: Keys to the key to node index map. - """ - return self._key_to_node_index.keys() - - def node_index(self, key): - """Return node index for a given key. - - Args: - key (Key): Key to an equivalence. - - Returns: - Int: Index to the node in the graph for the given key. - """ - return self._key_to_node_index[key] - def draw(self, filename=None): """Draws the equivalence relations available in the library. @@ -227,12 +61,13 @@ def _build_basis_graph(self): graph = rx.PyDiGraph() node_map = {} - for key in self._key_to_node_index: - name, num_qubits = key + for key in super().keys(): + name, num_qubits = key.name, key.num_qubits equivalences = self._get_equivalences(key) basis = frozenset([f"{name}/{num_qubits}"]) - for params, decomp in equivalences: + for equivalence in equivalences: + params, decomp = equivalence.params, equivalence.circuit decomp_basis = frozenset( f"{name}/{num_qubits}" for name, num_qubits in { @@ -257,39 +92,3 @@ def _build_basis_graph(self): ) return graph - - def _get_equivalences(self, key): - """Get all the equivalences for the given key""" - return ( - self._graph[self._key_to_node_index[key]].equivs - if key in self._key_to_node_index - else [] - ) - - -def _raise_if_param_mismatch(gate_params, circuit_parameters): - gate_parameters = [p for p in gate_params if isinstance(p, ParameterExpression)] - - if set(gate_parameters) != circuit_parameters: - raise CircuitError( - "Cannot add equivalence between circuit and gate " - f"of different parameters. Gate params: {gate_parameters}. " - f"Circuit params: {circuit_parameters}." - ) - - -def _raise_if_shape_mismatch(gate, circuit): - if gate.num_qubits != circuit.num_qubits or gate.num_clbits != circuit.num_clbits: - raise CircuitError( - "Cannot add equivalence between circuit and gate " - f"of different shapes. Gate: {gate.num_qubits} qubits and {gate.num_clbits} clbits. " - f"Circuit: {circuit.num_qubits} qubits and {circuit.num_clbits} clbits." - ) - - -def _rebind_equiv(equiv, query_params): - equiv_params, equiv_circuit = equiv - param_map = {x: y for x, y in zip(equiv_params, query_params) if isinstance(x, Parameter)} - equiv = equiv_circuit.assign_parameters(param_map, inplace=False, flat_input=True) - - return equiv diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 5737385af15b..0fdae973e1b9 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -13,6 +13,7 @@ """Translates gates to a target basis using a given equivalence library.""" +import random import time import logging @@ -32,7 +33,7 @@ ) from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.circuit.equivalence import Key, NodeData +from qiskit.circuit.equivalence import Key, NodeData, Equivalence from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES @@ -559,7 +560,7 @@ def _basis_search(equiv_lib, source_basis, target_basis): logger.debug("Begining basis search from %s to %s.", source_basis, target_basis) source_basis = { - (gate_name, gate_num_qubits) + Key(gate_name, gate_num_qubits) for gate_name, gate_num_qubits in source_basis if gate_name not in target_basis } @@ -577,7 +578,12 @@ def _basis_search(equiv_lib, source_basis, target_basis): # we add a dummy node and connect it with gates in the target basis. # we'll start the search from this dummy node. - dummy = graph.add_node(NodeData(key="key", equivs=[("dummy starting node", 0)])) + dummy = graph.add_node( + NodeData( + key=Key("".join(chr(random.randint(0, 26) + 97) for _ in range(10)), 0), + equivs=[Equivalence([], QuantumCircuit(0, name="dummy starting node"))], + ) + ) try: graph.add_edges_from_no_data( diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 31036de69645..733d5465fd0a 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -673,7 +673,7 @@ def _definitely_skip_node( or ( self._equiv_lib is not None and equivalence.Key(name=node.name, num_qubits=node.num_qubits) - in self._equiv_lib._key_to_node_index + in self._equiv_lib.keys() ) ) ) diff --git a/test/python/circuit/test_equivalence.py b/test/python/circuit/test_equivalence.py index 286941313ae9..0b523f7e5249 100644 --- a/test/python/circuit/test_equivalence.py +++ b/test/python/circuit/test_equivalence.py @@ -21,10 +21,10 @@ from qiskit.circuit import QuantumCircuit, Parameter, Gate from qiskit.circuit.library import U2Gate from qiskit.circuit.exceptions import CircuitError +from qiskit.circuit.quantumregister import Qubit from qiskit.converters import circuit_to_instruction, circuit_to_gate -from qiskit.circuit import EquivalenceLibrary +from qiskit.circuit.equivalence import EquivalenceLibrary, Key, Equivalence, NodeData, EdgeData from qiskit.utils import optionals -from qiskit.circuit.equivalence import Key, Equivalence, NodeData, EdgeData from test import QiskitTestCase # pylint: disable=wrong-import-order from ..visualization.visualization import QiskitVisualizationTestCase, path_to_diagram_reference @@ -70,7 +70,7 @@ def test_add_single_entry(self): eq_lib = EquivalenceLibrary() gate = OneQubitZeroParamGate() - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.h(0) eq_lib.add_equivalence(gate, equiv) @@ -86,12 +86,12 @@ def test_add_double_entry(self): eq_lib = EquivalenceLibrary() gate = OneQubitZeroParamGate() - first_equiv = QuantumCircuit(1) + first_equiv = QuantumCircuit([Qubit()]) first_equiv.h(0) eq_lib.add_equivalence(gate, first_equiv) - second_equiv = QuantumCircuit(1) + second_equiv = QuantumCircuit([Qubit()]) second_equiv.append(U2Gate(0, np.pi), [0]) eq_lib.add_equivalence(gate, second_equiv) @@ -110,12 +110,12 @@ def test_set_entry(self): gates = {key: Gate(key, 1, []) for key in "abcd"} target = Gate("target", 1, []) - old = QuantumCircuit(1) + old = QuantumCircuit([Qubit()]) old.append(gates["a"], [0]) old.append(gates["b"], [0]) eq_lib.add_equivalence(target, old) - outbound = QuantumCircuit(1) + outbound = QuantumCircuit([Qubit()]) outbound.append(target, [0]) eq_lib.add_equivalence(gates["c"], outbound) @@ -127,7 +127,7 @@ def test_set_entry(self): self.assertTrue(eq_lib.graph.has_edge(gate_indices["b"], gate_indices["target"])) self.assertTrue(eq_lib.graph.has_edge(gate_indices["target"], gate_indices["c"])) - new = QuantumCircuit(1) + new = QuantumCircuit([Qubit()]) new.append(gates["d"], [0]) eq_lib.set_entry(target, [new]) @@ -146,12 +146,12 @@ def test_set_entry_parallel_edges(self): gates = {key: Gate(key, 1, []) for key in "abcd"} target = Gate("target", 1, []) - old_1 = QuantumCircuit(1, name="a") + old_1 = QuantumCircuit([Qubit()], name="a") old_1.append(gates["a"], [0]) old_1.append(gates["b"], [0]) eq_lib.add_equivalence(target, old_1) - old_2 = QuantumCircuit(1, name="b") + old_2 = QuantumCircuit([Qubit()], name="b") old_2.append(gates["b"], [0]) old_2.append(gates["a"], [0]) eq_lib.add_equivalence(target, old_2) @@ -159,13 +159,13 @@ def test_set_entry_parallel_edges(self): # This extra rule is so that 'a' still has edges, so we can do an exact isomorphism test. # There's not particular requirement for `set_entry` to remove orphan nodes, so we'll just # craft a test that doesn't care either way. - a_to_b = QuantumCircuit(1) + a_to_b = QuantumCircuit([Qubit()]) a_to_b.append(gates["b"], [0]) eq_lib.add_equivalence(gates["a"], a_to_b) self.assertEqual(sorted(eq_lib.get_entry(target), key=lambda qc: qc.name), [old_1, old_2]) - new = QuantumCircuit(1, name="c") + new = QuantumCircuit([Qubit()], name="c") # No more use of 'a', but re-use 'b' and introduce 'c'. new.append(gates["b"], [0]) new.append(gates["c"], [0]) @@ -204,7 +204,7 @@ def test_has_entry(self): eq_lib = EquivalenceLibrary() gate = OneQubitZeroParamGate() - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.h(0) eq_lib.add_equivalence(gate, equiv) @@ -225,7 +225,7 @@ def test_equivalence_graph(self): eq_lib = EquivalenceLibrary() gate = OneQubitZeroParamGate() - first_equiv = QuantumCircuit(1) + first_equiv = QuantumCircuit([Qubit()]) first_equiv.h(0) eq_lib.add_equivalence(gate, first_equiv) @@ -282,7 +282,7 @@ def test_get_through_empty_library_to_base(self): base = EquivalenceLibrary() gate = OneQubitZeroParamGate() - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.h(0) base.add_equivalence(gate, equiv) @@ -299,13 +299,13 @@ def test_add_equivalence(self): base = EquivalenceLibrary() gate = OneQubitZeroParamGate() - first_equiv = QuantumCircuit(1) + first_equiv = QuantumCircuit([Qubit()]) first_equiv.h(0) base.add_equivalence(gate, first_equiv) eq_lib = EquivalenceLibrary(base=base) - second_equiv = QuantumCircuit(1) + second_equiv = QuantumCircuit([Qubit()]) second_equiv.append(U2Gate(0, np.pi), [0]) eq_lib.add_equivalence(gate, second_equiv) @@ -322,13 +322,13 @@ def test_set_entry(self): base = EquivalenceLibrary() gate = OneQubitZeroParamGate() - first_equiv = QuantumCircuit(1) + first_equiv = QuantumCircuit([Qubit()]) first_equiv.h(0) base.add_equivalence(gate, first_equiv) eq_lib = EquivalenceLibrary(base=base) - second_equiv = QuantumCircuit(1) + second_equiv = QuantumCircuit([Qubit()]) second_equiv.append(U2Gate(0, np.pi), [0]) eq_lib.set_entry(gate, [second_equiv]) @@ -344,7 +344,7 @@ def test_has_entry_in_base(self): base_eq_lib = EquivalenceLibrary() gate = OneQubitZeroParamGate() - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.h(0) base_eq_lib.add_equivalence(gate, equiv) @@ -355,7 +355,7 @@ def test_has_entry_in_base(self): self.assertTrue(eq_lib.has_entry(OneQubitZeroParamGate())) gate = OneQubitZeroParamGate() - equiv2 = QuantumCircuit(1) + equiv2 = QuantumCircuit([Qubit()]) equiv.append(U2Gate(0, np.pi), [0]) eq_lib.add_equivalence(gate, equiv2) @@ -383,7 +383,7 @@ def test_raise_if_gate_equiv_parameter_mismatch(self): phi = Parameter("phi") gate = OneQubitOneParamGate(theta) - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.p(phi, 0) with self.assertRaises(CircuitError): @@ -399,7 +399,7 @@ def test_parameter_in_parameter_out(self): theta = Parameter("theta") gate = OneQubitOneParamGate(theta) - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.p(theta, 0) eq_lib.add_equivalence(gate, equiv) @@ -409,7 +409,7 @@ def test_parameter_in_parameter_out(self): entry = eq_lib.get_entry(gate_phi) - expected = QuantumCircuit(1) + expected = QuantumCircuit([Qubit()]) expected.p(phi, 0) self.assertEqual(len(entry), 1) @@ -423,7 +423,7 @@ def test_partial_parameter_in_parameter_out(self): phi = Parameter("phi") gate = OneQubitTwoParamGate(theta, phi) - equiv = QuantumCircuit(1) + equiv = QuantumCircuit([Qubit()]) equiv.u(theta, phi, 0, 0) eq_lib.add_equivalence(gate, equiv) @@ -433,7 +433,7 @@ def test_partial_parameter_in_parameter_out(self): entry = eq_lib.get_entry(gate_partial) - expected = QuantumCircuit(1) + expected = QuantumCircuit([Qubit()]) expected.u(lam, 1.59, 0, 0) self.assertEqual(len(entry), 1) @@ -446,14 +446,14 @@ def test_adding_gate_under_different_parameters(self): theta = Parameter("theta") gate_theta = OneQubitOneParamGate(theta) - equiv_theta = QuantumCircuit(1) + equiv_theta = QuantumCircuit([Qubit()]) equiv_theta.p(theta, 0) eq_lib.add_equivalence(gate_theta, equiv_theta) phi = Parameter("phi") gate_phi = OneQubitOneParamGate(phi) - equiv_phi = QuantumCircuit(1) + equiv_phi = QuantumCircuit([Qubit()]) equiv_phi.rz(phi, 0) eq_lib.add_equivalence(gate_phi, equiv_phi) @@ -463,10 +463,10 @@ def test_adding_gate_under_different_parameters(self): entry = eq_lib.get_entry(gate_query) - first_expected = QuantumCircuit(1) + first_expected = QuantumCircuit([Qubit()]) first_expected.p(lam, 0) - second_expected = QuantumCircuit(1) + second_expected = QuantumCircuit([Qubit()]) second_expected.rz(lam, 0) self.assertEqual(len(entry), 2) @@ -482,13 +482,13 @@ def test_adding_gate_and_partially_specified_gate(self): # e.g. RGate(theta, phi) gate_full = OneQubitTwoParamGate(theta, phi) - equiv_full = QuantumCircuit(1) + equiv_full = QuantumCircuit([Qubit()]) equiv_full.append(U2Gate(theta, phi), [0]) eq_lib.add_equivalence(gate_full, equiv_full) gate_partial = OneQubitTwoParamGate(theta, 0) - equiv_partial = QuantumCircuit(1) + equiv_partial = QuantumCircuit([Qubit()]) equiv_partial.rx(theta, 0) eq_lib.add_equivalence(gate_partial, equiv_partial) @@ -498,10 +498,10 @@ def test_adding_gate_and_partially_specified_gate(self): entry = eq_lib.get_entry(gate_query) - first_expected = QuantumCircuit(1) + first_expected = QuantumCircuit([Qubit()]) first_expected.append(U2Gate(lam, 0), [0]) - second_expected = QuantumCircuit(1) + second_expected = QuantumCircuit([Qubit()]) second_expected.rx(lam, 0) self.assertEqual(len(entry), 2) @@ -514,7 +514,7 @@ class TestSessionEquivalenceLibrary(QiskitTestCase): def test_converter_gate_registration(self): """Verify converters register gates in session equivalence library.""" - qc_gate = QuantumCircuit(2) + qc_gate = QuantumCircuit([Qubit() for _ in range(2)]) qc_gate.h(0) qc_gate.cx(0, 1) @@ -522,7 +522,7 @@ def test_converter_gate_registration(self): bell_gate = circuit_to_gate(qc_gate, equivalence_library=sel) - qc_inst = QuantumCircuit(2) + qc_inst = QuantumCircuit([Qubit() for _ in range(2)]) qc_inst.h(0) qc_inst.cx(0, 1) @@ -539,7 +539,7 @@ def test_converter_gate_registration(self): def test_gate_decomposition_properties(self): """Verify decompositions are accessible via gate properties.""" - qc = QuantumCircuit(2) + qc = QuantumCircuit([Qubit() for _ in range(2)]) qc.h(0) qc.cx(0, 1) @@ -552,7 +552,7 @@ def test_gate_decomposition_properties(self): self.assertEqual(len(decomps), 1) self.assertEqual(decomps[0], qc) - qc2 = QuantumCircuit(2) + qc2 = QuantumCircuit([Qubit() for _ in range(2)]) qc2.h([0, 1]) qc2.cz(0, 1) qc2.h(1) @@ -582,12 +582,12 @@ def test_equivalence_draw(self): """Verify EquivalenceLibrary drawing with reference image.""" sel = EquivalenceLibrary() gate = OneQubitZeroParamGate() - first_equiv = QuantumCircuit(1) + first_equiv = QuantumCircuit([Qubit()]) first_equiv.h(0) sel.add_equivalence(gate, first_equiv) - second_equiv = QuantumCircuit(1) + second_equiv = QuantumCircuit([Qubit()]) second_equiv.append(U2Gate(0, np.pi), [0]) sel.add_equivalence(gate, second_equiv) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 914ac8bab985..b6f160a644d9 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -21,6 +21,7 @@ from qiskit import QuantumCircuit, QuantumRegister from qiskit.quantum_info import Operator from qiskit.circuit import ParameterVector, Gate, ControlledGate +from qiskit.circuit.quantumregister import Qubit from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.library import standard_gates from qiskit.circuit.library import ( @@ -402,11 +403,11 @@ def test_definition_parameters(self, gate_class): self.assertGreaterEqual(len(param_entry), 1) self.assertGreaterEqual(len(float_entry), 1) - param_qc = QuantumCircuit(param_gate.num_qubits) - float_qc = QuantumCircuit(float_gate.num_qubits) + param_qc = QuantumCircuit([Qubit() for _ in range(param_gate.num_qubits)]) + float_qc = QuantumCircuit([Qubit() for _ in range(float_gate.num_qubits)]) - param_qc.append(param_gate, param_qc.qregs[0]) - float_qc.append(float_gate, float_qc.qregs[0]) + param_qc.append(param_gate, param_qc.qubits) + float_qc.append(float_gate, float_qc.qubits) self.assertTrue(any(equiv == param_qc.decompose() for equiv in param_entry)) self.assertTrue(any(equiv == float_qc.decompose() for equiv in float_entry))