From 6a041eeeee5757b5482388415cdb8d662835be13 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:29:01 -0400 Subject: [PATCH 01/16] Add Rust representation of `EquivalenceLibrary` (#12585) * Initial: Add equivalence to `qiskit._accelerate.circuit` * Add: `build_basis_graph` method * Add: `EquivalencyLibrary` to `qiskit._accelerate.circuit` - Add `get_entry` method to obtain an entry from binding to a `QuantumCircuit`. - Add `rebind_equiv` to bind parameters to `QuantumCircuit` * Add: PyDiGraph converter for `equivalence.rs` * Add: Extend original equivalence with rust representation * Fix: Correct circuit parameter extraction * Add: Stable infrastructure for EquivalenceLibrary - TODO: Make elements pickleable. * Add: Default methods to equivalence data structures. * Fix: Adapt to new Gate Structure * Fix: Erroneous display of `Parameters` * Format: Fix lint test * Fix: Use EdgeReferences instead of edge_indices. - Remove stray comment. - Use match instead of matches!. * Fix: Use StableDiGraph for more stable indexing. - Remove required py argument for get_entry. - Reformat `to_pygraph` to use `add_nodes_from` and `add_edges_from`. - Other small fixes. * Fix: Use `clone` instead of `to_owned` - Use `clone_ref` for the PyObject Graph instance. * Fix: Use `OperationTypeConstruct` instead of `CircuitInstruction` - Use `convert_py_to_operation_type` to correctly extract Gate instances into rust operational datatypes. - Add function `get_sources_from_circuit_rep` to not extract circuit data directly but only the necessary data. - Modify failing test due to different mapping. (!!) - Other tweaks and fixes. * Fix: Elide implicit lifetime of PyRef * Fix: Make `CircuitRep` attributes OneCell-like. - Attributes from CircuitRep are only written once, reducing the overhead. - Modify `__setstate__` to avoid extra conversion. - Remove `get_sources_from_circuit_rep`. * Fix: Incorrect pickle attribute extraction * Remove: Default initialization methods from custom datatypes. - Use `__getnewargs__ instead. * Remove: `__getstate__`, `__setstate__`, use `__getnewargs__` instead. * Fix: Further improvements to pickling - Use python structures to avoid extra conversions. - Add rust native `EquivalenceLibrary.keys()` and have the python method use it. * Fix: Use `PyList` and iterators when possible to skip extra conversion. - Use a `py` token instead of `Python::with_gil()` for `rebind_params`. - Other tweaks and fixes. * Fix: incorrect list operation in `__getstate__` * Fix: improvements on rust native methods - Accept `Operations` and `[Param]` instead of the custom `GateOper` when calling from rust. - Build custom `GateOper` inside of class. * Remove: `add_equiv`, `set_entry` from rust-native methods. - Add `node_index` Rust native method. - Use python set comparison for `Param` check. * Remove: Undo changes to Param - Fix comparison methods for `Key`, `Equivalence`, `EdgeData` and `NodeData` to account for the removal of `PartialEq` for `Param`. * Fix: Leverage usage of `CircuitData` for accessing the `QuantumCircuit` intructions in rust. - Change implementation of `CircuitRef, to leverage the use of `CircuitData`. * Add: `data()` method to avoid extracting `CircuitData` - Add `py_clone` to perform shallow clones of a `CircuitRef` object by cloning the references to the `QuantumCircuit` object. - Extract `num_qubits` and `num_clbits` for CircuitRep. - Add wrapper over `add_equivalence` to be able to accept references and avoid unnecessary cloning of `GateRep` objects in `set_entry`. - Remove stray mutability of `entry` in `set_entry`. * Fix: Make `graph` attribute public. * Fix: Make `NoteData` attributes public. * Fix: Revert reference to `CircuitData`, extract instead. * Add: Make `EquivalenceLibrary` graph weights optional. * Fix: Adapt to #12730 * Fix: Use `IndexSet` and `IndexMap` * Fix: Revert changes from previously failing test * Fix: Adapt to #12974 * Fix: Use `EquivalenceLibrary.keys()` instead of `._key_to_node_index` * Chore: update dependencies * Refactor: Move `EquivalenceLibrary` to `_accelerate`. * Fix: Erroneous `pymodule` function for `equivalence`. * Fix: Update `EquivalenceLibrary` to store `CircuitData`. - The equivalence library will now only store `CircuitData` instances as it does not need to call python directly to re-assign parameters. - An `IntoPy` trait was adapted so that it can be automatically converted to a `QuantumCircuit` instance using `_from_circuit_data`. - Updated all tests to use register-less qubits in circuit comparison. - Remove `num_qubits` and `num_clbits` from `CircuitRep`. * Fix: Make inner `CircuitData` instance public. * Fix: Review comments and ownership issues. - Add `from_operation` constructor for `Key`. - Made `py_has_entry()` private, but kept its main method public. - Made `set_entry` more rust friendly. - Modify `add_equivalence` to accept a slice of `Param` and use `Into` to convert it into a `SmallVec` instance. * Fix: Use maximum possible integer value for Key in basis_translator. - Add method to immutably borrow the `EquivalenceLibrary`'s graph. * Fix: Use generated string, instead of large int - Using large int as the key's number of qubits breaks compatibility with qpy, use a random string instead. --------- Co-authored-by: John Lapeyre --- crates/accelerate/src/equivalence.rs | 805 ++++++++++++++++++ crates/accelerate/src/lib.rs | 1 + crates/circuit/Cargo.toml | 2 +- crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/circuit/equivalence.py | 227 +---- .../passes/basis/basis_translator.py | 12 +- .../passes/synthesis/high_level_synthesis.py | 2 +- test/python/circuit/test_equivalence.py | 80 +- test/python/circuit/test_gate_definitions.py | 9 +- 10 files changed, 877 insertions(+), 263 deletions(-) create mode 100644 crates/accelerate/src/equivalence.rs diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs new file mode 100644 index 00000000000..e389151b6b0 --- /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 9111f932e27..f17fea5bccf 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 ed1f849bbf6..ef79525ab76 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 675cdfb3961..8b54c535db2 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 25137d7a591..0e9694217cd 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 17912517d24..05441e68e60 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 5737385af15..0fdae973e1b 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 31036de6964..733d5465fd0 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 286941313ae..0b523f7e524 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 914ac8bab98..b6f160a644d 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)) From 173a6440607dfc5adc9e82bc7e0ce18649b082b2 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 25 Sep 2024 12:53:11 -0400 Subject: [PATCH 02/16] Remove examples/ directory (#13218) * Remove examples/ directory This commit removes the examples/ directory from Qiskit. At one time in the past these scripts and qasm files served as a useful example for how to get started with Qiskit. However the patterns shown in these examples aren't very relevant anymore and have been superseded by the official documentation and the numerous tutorials out there now. While these examples still work as we've been running tests to validate they're still functional on every commit, there is little value in keeping them around. This commit removes the examples directory and all the files contained within. The test_examples unittest module is also removed since there is nothing left to test anymore. * Remove other examples usage in Makefile and CONTRIBUTING.md --- CONTRIBUTING.md | 8 +- Makefile | 12 +- examples/python/circuit_draw.py | 33 ----- examples/python/commutation_relation.py | 37 ------ examples/python/ghz.py | 41 ------- examples/python/initialize.py | 70 ----------- examples/python/load_qasm.py | 30 ----- examples/python/qft.py | 76 ------------ examples/python/rippleadd.py | 113 ------------------ examples/python/stochastic_swap.py | 96 --------------- examples/python/teleport.py | 82 ------------- examples/python/using_qiskit_terra_level_0.py | 43 ------- examples/qasm/entangled_registers.qasm | 20 ---- examples/qasm/plaquette_check.qasm | 21 ---- examples/qasm/simple.qasm | 5 - test/python/test_examples.py | 59 --------- tox.ini | 18 ++- 17 files changed, 16 insertions(+), 748 deletions(-) delete mode 100644 examples/python/circuit_draw.py delete mode 100644 examples/python/commutation_relation.py delete mode 100644 examples/python/ghz.py delete mode 100644 examples/python/initialize.py delete mode 100644 examples/python/load_qasm.py delete mode 100644 examples/python/qft.py delete mode 100644 examples/python/rippleadd.py delete mode 100644 examples/python/stochastic_swap.py delete mode 100644 examples/python/teleport.py delete mode 100644 examples/python/using_qiskit_terra_level_0.py delete mode 100644 examples/qasm/entangled_registers.qasm delete mode 100644 examples/qasm/plaquette_check.qasm delete mode 100644 examples/qasm/simple.qasm delete mode 100644 test/python/test_examples.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53043ffaa28..e196a0fe733 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -440,21 +440,21 @@ you can do this faster with the `-n`/`--no-discover` option. For example: to run a module: ``` -tox -epy310 -- -n test.python.test_examples +tox -epy310 -- -n test.python.compiler.test_transpiler ``` or to run the same module by path: ``` -tox -epy310 -- -n test/python/test_examples.py +tox -epy310 -- -n test/python/compiler/test_transpiler.py ``` to run a class: ``` -tox -epy310 -- -n test.python.test_examples.TestPythonExamples +tox -epy310 -- -n test.python.compiler.test_transpiler.TestTranspile ``` to run a method: ``` -tox -epy310 -- -n test.python.test_examples.TestPythonExamples.test_all_examples +tox -epy310 -- -n test.python.compiler.test_transpiler.TestTranspile.test_transpile_non_adjacent_layout ``` Alternatively there is a makefile provided to run tests, however this diff --git a/Makefile b/Makefile index bea8a880e27..b2f37105d41 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,7 @@ env: # Ignoring generated ones with .py extension. lint: pylint -rn qiskit test tools - tools/verify_headers.py qiskit test tools examples - pylint -rn --disable='invalid-name, missing-module-docstring, redefined-outer-name' examples/python/*.py + tools/verify_headers.py qiskit test tools tools/find_optional_imports.py tools/find_stray_release_notes.py @@ -37,18 +36,17 @@ lint: lint-incr: -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest tools/pylint_incr.py -j4 -rn -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py - tools/pylint_incr.py -j4 -rn -sn --disable='invalid-name, missing-module-docstring, redefined-outer-name' --paths ':(glob,top)examples/python/*.py' - tools/verify_headers.py qiskit test tools examples + tools/verify_headers.py qiskit test tools tools/find_optional_imports.py ruff: - ruff qiskit test tools examples setup.py + ruff qiskit test tools setup.py style: - black --check qiskit test tools examples setup.py + black --check qiskit test tools setup.py black: - black qiskit test tools examples setup.py + black qiskit test tools setup.py # Use the -s (starting directory) flag for "unittest discover" is necessary, # otherwise the QuantumCircuit header will be modified during the discovery. diff --git a/examples/python/circuit_draw.py b/examples/python/circuit_draw.py deleted file mode 100644 index b8efbbef491..00000000000 --- a/examples/python/circuit_draw.py +++ /dev/null @@ -1,33 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -""" -Example showing how to draw a quantum circuit using Qiskit. -""" - -from qiskit import QuantumCircuit - - -def build_bell_circuit(): - """Returns a circuit putting 2 qubits in the Bell state.""" - qc = QuantumCircuit(2, 2) - qc.h(0) - qc.cx(0, 1) - qc.measure([0, 1], [0, 1]) - return qc - - -# Create the circuit -bell_circuit = build_bell_circuit() - -# Use the internal .draw() to print the circuit -print(bell_circuit) diff --git a/examples/python/commutation_relation.py b/examples/python/commutation_relation.py deleted file mode 100644 index 6dd71981fa2..00000000000 --- a/examples/python/commutation_relation.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -from qiskit import QuantumCircuit - -from qiskit.transpiler import PassManager -from qiskit.transpiler.passes import CommutationAnalysis, CommutativeCancellation - -circuit = QuantumCircuit(5) -# Quantum Instantaneous Polynomial Time example -circuit.cx(0, 1) -circuit.cx(2, 1) -circuit.cx(4, 3) -circuit.cx(2, 3) -circuit.z(0) -circuit.z(4) -circuit.cx(0, 1) -circuit.cx(2, 1) -circuit.cx(4, 3) -circuit.cx(2, 3) -circuit.cx(3, 2) - -print(circuit) - -pm = PassManager() -pm.append([CommutationAnalysis(), CommutativeCancellation()]) -new_circuit = pm.run(circuit) -print(new_circuit) diff --git a/examples/python/ghz.py b/examples/python/ghz.py deleted file mode 100644 index 51bafa9c2f6..00000000000 --- a/examples/python/ghz.py +++ /dev/null @@ -1,41 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -""" -GHZ state example. It also compares running on experiment and simulator. -""" - -from qiskit import QuantumCircuit, transpile -from qiskit.providers.basic_provider import BasicSimulator - - -############################################################### -# Make a quantum circuit for the GHZ state. -############################################################### -num_qubits = 5 -qc = QuantumCircuit(num_qubits, num_qubits, name="ghz") - -# Create a GHZ state -qc.h(0) -for i in range(num_qubits - 1): - qc.cx(i, i + 1) -# Insert a barrier before measurement -qc.barrier() -# Measure all of the qubits in the standard basis -for i in range(num_qubits): - qc.measure(i, i) - -sim_backend = BasicSimulator() -job = sim_backend.run(transpile(qc, sim_backend), shots=1024) -result = job.result() -print("Basic simulator : ") -print(result.get_counts(qc)) diff --git a/examples/python/initialize.py b/examples/python/initialize.py deleted file mode 100644 index 3e47922d330..00000000000 --- a/examples/python/initialize.py +++ /dev/null @@ -1,70 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -""" -Example use of the initialize gate to prepare arbitrary pure states. -""" - -import math -from qiskit import QuantumCircuit, transpile -from qiskit.providers.basic_provider import BasicSimulator - - -############################################################### -# Make a quantum circuit for state initialization. -############################################################### -circuit = QuantumCircuit(4, 4, name="initializer_circ") - -desired_vector = [ - 1 / math.sqrt(4) * complex(0, 1), - 1 / math.sqrt(8) * complex(1, 0), - 0, - 0, - 0, - 0, - 0, - 0, - 1 / math.sqrt(8) * complex(1, 0), - 1 / math.sqrt(8) * complex(0, 1), - 0, - 0, - 0, - 0, - 1 / math.sqrt(4) * complex(1, 0), - 1 / math.sqrt(8) * complex(1, 0), -] - -circuit.initialize(desired_vector, [0, 1, 2, 3]) - -circuit.measure([0, 1, 2, 3], [0, 1, 2, 3]) - -print(circuit) - -############################################################### -# Execute on a simulator backend. -############################################################### -shots = 10000 - -# Desired vector -print("Desired probabilities: ") -print([format(abs(x * x), ".3f") for x in desired_vector]) - -# Initialize on local simulator -sim_backend = BasicSimulator() -job = sim_backend.run(transpile(circuit, sim_backend), shots=shots) -result = job.result() - -counts = result.get_counts(circuit) - -qubit_strings = [format(i, "04b") for i in range(2**4)] -print("Probabilities from simulator: ") -print([format(counts.get(s, 0) / shots, ".3f") for s in qubit_strings]) diff --git a/examples/python/load_qasm.py b/examples/python/load_qasm.py deleted file mode 100644 index af4878c4186..00000000000 --- a/examples/python/load_qasm.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -"""Example on how to load a file into a QuantumCircuit.""" - -from qiskit import QuantumCircuit -from qiskit.providers.basic_provider import BasicSimulator - -circ = QuantumCircuit.from_qasm_file("examples/qasm/entangled_registers.qasm") -print(circ) - -# See the backend -sim_backend = BasicSimulator() - -# Compile and run the Quantum circuit on a local simulator backend -job_sim = sim_backend.run(circ) -sim_result = job_sim.result() - -# Show the results -print("simulation: ", sim_result) -print(sim_result.get_counts(circ)) diff --git a/examples/python/qft.py b/examples/python/qft.py deleted file mode 100644 index ca22d0d6053..00000000000 --- a/examples/python/qft.py +++ /dev/null @@ -1,76 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# 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. - -""" -Quantum Fourier Transform examples. -""" - -import math -from qiskit import QuantumCircuit -from qiskit import transpile -from qiskit.providers.basic_provider import BasicSimulator - - -############################################################### -# make the qft -############################################################### -def input_state(circ, n): - """n-qubit input state for QFT that produces output 1.""" - for j in range(n): - circ.h(j) - circ.p(-math.pi / float(2 ** (j)), j) - - -def qft(circ, n): - """n-qubit QFT on q in circ.""" - for j in range(n): - for k in range(j): - circ.cp(math.pi / float(2 ** (j - k)), j, k) - circ.h(j) - - -qft3 = QuantumCircuit(5, 5, name="qft3") -qft4 = QuantumCircuit(5, 5, name="qft4") -qft5 = QuantumCircuit(5, 5, name="qft5") - -input_state(qft3, 3) -qft3.barrier() -qft(qft3, 3) -qft3.barrier() -for j in range(3): - qft3.measure(j, j) - -input_state(qft4, 4) -qft4.barrier() -qft(qft4, 4) -qft4.barrier() -for j in range(4): - qft4.measure(j, j) - -input_state(qft5, 5) -qft5.barrier() -qft(qft5, 5) -qft5.barrier() -for j in range(5): - qft5.measure(j, j) - -print(qft3) -print(qft4) -print(qft5) - -print("Basic simulator") -sim_backend = BasicSimulator() -job = sim_backend.run(transpile([qft3, qft4, qft5], sim_backend), shots=1024) -result = job.result() -print(result.get_counts(qft3)) -print(result.get_counts(qft4)) -print(result.get_counts(qft5)) diff --git a/examples/python/rippleadd.py b/examples/python/rippleadd.py deleted file mode 100644 index 6cb40f424c5..00000000000 --- a/examples/python/rippleadd.py +++ /dev/null @@ -1,113 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -""" -Ripple adder example based on Cuccaro et al., quant-ph/0410184. - -""" - -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit import transpile -from qiskit.providers.basic_provider import BasicSimulator - -############################################################### -# Set the backend name and coupling map. -############################################################### -backend = BasicSimulator() -coupling_map = [ - [0, 1], - [0, 8], - [1, 2], - [1, 9], - [2, 3], - [2, 10], - [3, 4], - [3, 11], - [4, 5], - [4, 12], - [5, 6], - [5, 13], - [6, 7], - [6, 14], - [7, 15], - [8, 9], - [9, 10], - [10, 11], - [11, 12], - [12, 13], - [13, 14], - [14, 15], -] - -############################################################### -# Make a quantum program for the n-bit ripple adder. -############################################################### -n = 2 - -a = QuantumRegister(n, "a") -b = QuantumRegister(n, "b") -cin = QuantumRegister(1, "cin") -cout = QuantumRegister(1, "cout") -ans = ClassicalRegister(n + 1, "ans") -qc = QuantumCircuit(a, b, cin, cout, ans, name="rippleadd") - - -def majority(p, a, b, c): - """Majority gate.""" - p.cx(c, b) - p.cx(c, a) - p.ccx(a, b, c) - - -def unmajority(p, a, b, c): - """Unmajority gate.""" - p.ccx(a, b, c) - p.cx(c, a) - p.cx(a, b) - - -# Build a temporary subcircuit that adds a to b, -# storing the result in b -adder_subcircuit = QuantumCircuit(cin, a, b, cout) -majority(adder_subcircuit, cin[0], b[0], a[0]) -for j in range(n - 1): - majority(adder_subcircuit, a[j], b[j + 1], a[j + 1]) -adder_subcircuit.cx(a[n - 1], cout[0]) -for j in reversed(range(n - 1)): - unmajority(adder_subcircuit, a[j], b[j + 1], a[j + 1]) -unmajority(adder_subcircuit, cin[0], b[0], a[0]) - -# Set the inputs to the adder -qc.x(a[0]) # Set input a = 0...0001 -qc.x(b) # Set input b = 1...1111 -# Apply the adder -qc &= adder_subcircuit -# Measure the output register in the computational basis -for j in range(n): - qc.measure(b[j], ans[j]) -qc.measure(cout[0], ans[n]) - -############################################################### -# execute the program. -############################################################### - -# First version: not mapped -job = backend.run(transpile(qc, backend=backend, coupling_map=None), shots=1024) -result = job.result() -print(result.get_counts(qc)) - -# Second version: mapped to 2x8 array coupling graph -job = backend.run(transpile(qc, basis_gates=["u", "cx"], coupling_map=coupling_map), shots=1024) -result = job.result() -print(result.get_counts(qc)) - -# Both versions should give the same distribution diff --git a/examples/python/stochastic_swap.py b/examples/python/stochastic_swap.py deleted file mode 100644 index 7625cba3b73..00000000000 --- a/examples/python/stochastic_swap.py +++ /dev/null @@ -1,96 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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. - -"""Example of using the StochasticSwap pass.""" - -from qiskit.transpiler.passes import StochasticSwap -from qiskit.transpiler import CouplingMap -from qiskit.converters import circuit_to_dag -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit - -coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) -qr = QuantumRegister(4, "q") -cr = ClassicalRegister(4, "c") -circ = QuantumCircuit(qr, cr) -circ.cx(qr[1], qr[2]) -circ.cx(qr[0], qr[3]) -circ.measure(qr[0], cr[0]) -circ.h(qr) -circ.cx(qr[0], qr[1]) -circ.cx(qr[2], qr[3]) -circ.measure(qr[0], cr[0]) -circ.measure(qr[1], cr[1]) -circ.measure(qr[2], cr[2]) -circ.measure(qr[3], cr[3]) -dag = circuit_to_dag(circ) -# ┌─┐┌───┐ ┌─┐ -# q_0: |0>─────────────────■──────────────────┤M├┤ H ├──■─────┤M├ -# ┌───┐ │ └╥┘└───┘┌─┴─┐┌─┐└╥┘ -# q_1: |0>──■───────┤ H ├──┼───────────────────╫──────┤ X ├┤M├─╫─ -# ┌─┴─┐┌───┐└───┘ │ ┌─┐ ║ └───┘└╥┘ ║ -# q_2: |0>┤ X ├┤ H ├───────┼─────────■─────┤M├─╫────────────╫──╫─ -# └───┘└───┘ ┌─┴─┐┌───┐┌─┴─┐┌─┐└╥┘ ║ ║ ║ -# q_3: |0>───────────────┤ X ├┤ H ├┤ X ├┤M├─╫──╫────────────╫──╫─ -# └───┘└───┘└───┘└╥┘ ║ ║ ║ ║ -# c_0: 0 ═══════════════════════════════╬══╬══╩════════════╬══╩═ -# ║ ║ ║ -# c_1: 0 ═══════════════════════════════╬══╬═══════════════╩════ -# ║ ║ -# c_2: 0 ═══════════════════════════════╬══╩════════════════════ -# ║ -# c_3: 0 ═══════════════════════════════╩═══════════════════════ -# -# ┌─┐┌───┐ ┌─┐ -# q_0: |0>────────────────────■──┤M├┤ H ├──────────────────■──┤M├────── -# ┌─┴─┐└╥┘└───┘┌───┐┌───┐ ┌─┴─┐└╥┘┌─┐ -# q_1: |0>──■───X───────────┤ X ├─╫──────┤ H ├┤ X ├─X────┤ X ├─╫─┤M├─── -# ┌─┴─┐ │ ┌───┐└───┘ ║ └───┘└─┬─┘ │ └───┘ ║ └╥┘┌─┐ -# q_2: |0>┤ X ├─┼──────┤ H ├──────╫─────────────■───┼──────────╫──╫─┤M├ -# └───┘ │ ┌───┐└───┘ ║ │ ┌─┐ ║ ║ └╥┘ -# q_3: |0>──────X─┤ H ├───────────╫─────────────────X─┤M├──────╫──╫──╫─ -# └───┘ ║ └╥┘ ║ ║ ║ -# c_0: 0 ════════════════════════╩════════════════════╬═══════╩══╬══╬═ -# ║ ║ ║ -# c_1: 0 ═════════════════════════════════════════════╬══════════╩══╬═ -# ║ ║ -# c_2: 0 ═════════════════════════════════════════════╬═════════════╩═ -# ║ -# c_3: 0 ═════════════════════════════════════════════╩═══════════════ -# -# -# 2 -# | -# 0 - 1 - 3 -# Build the expected output to verify the pass worked -expected = QuantumCircuit(qr, cr) -expected.cx(qr[1], qr[2]) -expected.h(qr[2]) -expected.swap(qr[0], qr[1]) -expected.h(qr[0]) -expected.cx(qr[1], qr[3]) -expected.h(qr[3]) -expected.measure(qr[1], cr[0]) -expected.swap(qr[1], qr[3]) -expected.cx(qr[2], qr[1]) -expected.h(qr[3]) -expected.swap(qr[0], qr[1]) -expected.measure(qr[2], cr[2]) -expected.cx(qr[3], qr[1]) -expected.measure(qr[0], cr[3]) -expected.measure(qr[3], cr[0]) -expected.measure(qr[1], cr[1]) -expected_dag = circuit_to_dag(expected) -# Run the pass on the dag from the input circuit -pass_ = StochasticSwap(coupling, 20, 999) -after = pass_.run(dag) -# Verify the output of the pass matches our expectation -assert expected_dag == after diff --git a/examples/python/teleport.py b/examples/python/teleport.py deleted file mode 100644 index 73d7fd61d22..00000000000 --- a/examples/python/teleport.py +++ /dev/null @@ -1,82 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -""" -Quantum teleportation example. - -Note: if you have only cloned the Qiskit repository but not -used `pip install`, the examples only work from the root directory. -""" - -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit -from qiskit import transpile -from qiskit.providers.basic_provider import BasicSimulator - -############################################################### -# Set the backend name and coupling map. -############################################################### -coupling_map = [[0, 1], [0, 2], [1, 2], [3, 2], [3, 4], [4, 2]] -backend = BasicSimulator() - -############################################################### -# Make a quantum program for quantum teleportation. -############################################################### -q = QuantumRegister(3, "q") -c0 = ClassicalRegister(1, "c0") -c1 = ClassicalRegister(1, "c1") -c2 = ClassicalRegister(1, "c2") -qc = QuantumCircuit(q, c0, c1, c2, name="teleport") - -# Prepare an initial state -qc.u(0.3, 0.2, 0.1, q[0]) - -# Prepare a Bell pair -qc.h(q[1]) -qc.cx(q[1], q[2]) - -# Barrier following state preparation -qc.barrier(q) - -# Measure in the Bell basis -qc.cx(q[0], q[1]) -qc.h(q[0]) -qc.measure(q[0], c0[0]) -qc.measure(q[1], c1[0]) - -# Apply a correction -qc.barrier(q) -qc.z(q[2]).c_if(c0, 1) -qc.x(q[2]).c_if(c1, 1) -qc.measure(q[2], c2[0]) - -############################################################### -# Execute. -# Experiment does not support feedback, so we use the simulator -############################################################### - -# First version: not mapped -initial_layout = {q[0]: 0, q[1]: 1, q[2]: 2} -job = backend.run( - transpile(qc, backend=backend, coupling_map=None, initial_layout=initial_layout), shots=1024 -) - -result = job.result() -print(result.get_counts(qc)) - -# Second version: mapped to 2x8 array coupling graph -job = backend.run( - transpile(qc, backend=backend, coupling_map=coupling_map, initial_layout=initial_layout), - shots=1024, -) -result = job.result() -print(result.get_counts(qc)) -# Both versions should give the same distribution diff --git a/examples/python/using_qiskit_terra_level_0.py b/examples/python/using_qiskit_terra_level_0.py deleted file mode 100644 index cd92701d22a..00000000000 --- a/examples/python/using_qiskit_terra_level_0.py +++ /dev/null @@ -1,43 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# 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. - -""" -Example showing how to use Qiskit at introduction level. - -This example shows the most basic way to use Qiskit. It builds some circuits -and runs them on the BasicProvider (local Qiskit provider). -""" - -# Import the Qiskit modules -from qiskit import QuantumCircuit -from qiskit import transpile -from qiskit.providers.basic_provider import BasicSimulator - -# making first circuit: bell state -qc1 = QuantumCircuit(2, 2) -qc1.h(0) -qc1.cx(0, 1) -qc1.measure([0, 1], [0, 1]) - -# making another circuit: superpositions -qc2 = QuantumCircuit(2, 2) -qc2.h([0, 1]) -qc2.measure([0, 1], [0, 1]) - -# running the job -sim_backend = BasicSimulator() -job_sim = sim_backend.run(transpile([qc1, qc2], sim_backend)) -sim_result = job_sim.result() - -# Show the results -print(sim_result.get_counts(qc1)) -print(sim_result.get_counts(qc2)) diff --git a/examples/qasm/entangled_registers.qasm b/examples/qasm/entangled_registers.qasm deleted file mode 100644 index ad3f099342b..00000000000 --- a/examples/qasm/entangled_registers.qasm +++ /dev/null @@ -1,20 +0,0 @@ -// A simple 8 qubit example entangling two 4 qubit registers -OPENQASM 2.0; -include "qelib1.inc"; - -qreg a[4]; -qreg b[4]; -creg c[4]; -creg d[4]; -h a; -cx a, b; -barrier a; -barrier b; -measure a[0]->c[0]; -measure a[1]->c[1]; -measure a[2]->c[2]; -measure a[3]->c[3]; -measure b[0]->d[0]; -measure b[1]->d[1]; -measure b[2]->d[2]; -measure b[3]->d[3]; diff --git a/examples/qasm/plaquette_check.qasm b/examples/qasm/plaquette_check.qasm deleted file mode 100644 index 9b5a6e94ef9..00000000000 --- a/examples/qasm/plaquette_check.qasm +++ /dev/null @@ -1,21 +0,0 @@ -// plaquette check -OPENQASM 2.0; -include "qelib1.inc"; - -qreg q[5]; -creg c[5]; - -x q[1]; -x q[4]; -barrier q; - -cx q[0], q[2]; -cx q[1], q[2]; -cx q[3], q[2]; -cx q[4], q[2]; -barrier q; -measure q[0]->c[0]; -measure q[1]->c[1]; -measure q[2]->c[2]; -measure q[3]->c[3]; -measure q[4]->c[4]; diff --git a/examples/qasm/simple.qasm b/examples/qasm/simple.qasm deleted file mode 100644 index c034011d51f..00000000000 --- a/examples/qasm/simple.qasm +++ /dev/null @@ -1,5 +0,0 @@ -include "qelib1.inc"; -qreg q[2]; - -h q[0]; -cx q[0],q[1]; diff --git a/test/python/test_examples.py b/test/python/test_examples.py deleted file mode 100644 index fc08ff3abaf..00000000000 --- a/test/python/test_examples.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# 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. - -"""Test examples scripts.""" - -import os -import subprocess -import sys -import unittest - -from test import QiskitTestCase - -examples_dir = os.path.abspath( - os.path.join( - os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "examples"), - "python", - ) -) -ibmq_examples_dir = os.path.join(examples_dir, "ibmq") - - -class TestPythonExamples(QiskitTestCase): - """Test example scripts""" - - @unittest.skipIf( - sys.platform == "darwin", - "Multiprocess spawn fails on macOS python >=3.8 without __name__ == '__main__' guard", - ) - def test_all_examples(self): - """Execute the example python files and pass if it returns 0.""" - examples = [] - if os.path.isdir(examples_dir): - examples = [x for x in os.listdir(examples_dir) if x.endswith(".py")] - for example in examples: - with self.subTest(example=example): - example_path = os.path.join(examples_dir, example) - cmd = [sys.executable, example_path] - with subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env={**os.environ, "PYTHONIOENCODING": "utf8"}, - ) as run_example: - stdout, stderr = run_example.communicate() - error_string = ( - f"Running example {example} failed with return code" - f"{run_example.returncode}\n" - f"stdout:{stdout}\nstderr: {stderr}" - ) - self.assertEqual(run_example.returncode, 0, error_string) diff --git a/tox.ini b/tox.ini index a0c5665695d..5456e773192 100644 --- a/tox.ini +++ b/tox.ini @@ -39,15 +39,12 @@ basepython = python3 package = editable allowlist_externals = cargo commands = - black --check {posargs} qiskit test tools examples setup.py + black --check {posargs} qiskit test tools setup.py cargo fmt --check - ruff check qiskit test tools examples setup.py + ruff check qiskit test tools setup.py cargo clippy -- -D warnings pylint -rn qiskit test tools - # This line is commented out until #6649 merges. We can't run this currently - # via tox because tox doesn't support globbing - # pylint -rn --disable='invalid-name,missing-module-docstring,redefined-outer-name' examples/python/*.py - python {toxinidir}/tools/verify_headers.py qiskit test tools examples crates + python {toxinidir}/tools/verify_headers.py qiskit test tools crates python {toxinidir}/tools/find_optional_imports.py python {toxinidir}/tools/find_stray_release_notes.py reno -q lint @@ -56,12 +53,11 @@ commands = basepython = python3 allowlist_externals = git commands = - ruff check qiskit test tools examples setup.py - black --check {posargs} qiskit test tools examples setup.py + ruff check qiskit test tools setup.py + black --check {posargs} qiskit test tools setup.py -git fetch -q https://github.com/Qiskit/qiskit.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py - python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --disable='invalid-name,missing-module-docstring,redefined-outer-name' --paths :(glob,top)examples/python/*.py - python {toxinidir}/tools/verify_headers.py qiskit test tools examples crates + python {toxinidir}/tools/verify_headers.py qiskit test tools crates python {toxinidir}/tools/find_optional_imports.py python {toxinidir}/tools/find_stray_release_notes.py reno -q lint @@ -70,7 +66,7 @@ commands = skip_install = true deps = -r requirements-dev.txt -commands = black {posargs} qiskit test tools examples setup.py +commands = black {posargs} qiskit test tools setup.py [testenv:coverage] basepython = python3 From ed53f4323d1e3a8835ce163a4a9e9ecd3c8934e0 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:58:33 -0400 Subject: [PATCH 03/16] Add header to qpy file for footnotes (#13200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add header to qpy file for footnotes * Call it references Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit/qpy/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 42c7466d449..eccb09ce73d 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -1621,6 +1621,8 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom this matches the internal C representation of Python's complex type. [#f3]_ +References +========== .. [#f1] https://tools.ietf.org/html/rfc1700 .. [#f2] https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html From 77b571db090cf8b002f326f8d9c3655dcd1ec460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:33:38 -0400 Subject: [PATCH 04/16] Bump faer from 0.19.3 to 0.19.4 (#13208) Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.19.3 to 0.19.4. - [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sarah-ek/faer-rs/commits) --- updated-dependencies: - dependency-name: faer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15f5a7833ca..18eaf0be9e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,9 +345,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "faer" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0821d176d7fd17ea91d8a5ce84c56413e11410f404e418bfd205acf40184d819" +checksum = "64bc4855cb2792ae3520e8af22051a47a6d6dc8300ebc0ddf51ad73f65bd0dc9" dependencies = [ "bytemuck", "coe-rs", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d91523cd7cc..df8c2436a5c 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,7 +20,7 @@ num-traits = "0.2" num-complex.workspace = true rustworkx-core.workspace = true num-bigint.workspace = true -faer = "0.19.3" +faer = "0.19.4" itertools.workspace = true qiskit-circuit.workspace = true thiserror.workspace = true From a3015f76e04a4b8771342eddae5aaa68c046414d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:33:56 -0400 Subject: [PATCH 05/16] Bump thiserror from 1.0.63 to 1.0.64 (#13209) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.63 to 1.0.64. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18eaf0be9e6..3de6bc61961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1633,18 +1633,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", From e3f5c25b22a6ced7a0ed231abe503b0898eda2d6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Sep 2024 07:29:44 -0400 Subject: [PATCH 06/16] Deprecate the unit and duration attributes (#13224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deprecate the unit and duration attributes This commit deprecates the unit and duration attributes for QuantumCircuit and Instruction. These attributes will be removed in Qiskit 2.0 as they're not needed anymore and are adding a lot of complexity to the circuit data model as they're mutable state and extra memory slots that we need to keep around. The better model for tracking instruction duration is in the Target as it's inherently a property of the backend running the instructions. For the unittests this commit globally ignores the deprecation warning raised by this commit because internally we access the now deprecated access quite frequently as we need to check it when adding instructions to circuits to populate the rust data model. * Apply suggestions from code review Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit/circuit/instruction.py | 2 ++ qiskit/circuit/quantumcircuit.py | 30 +++++++++++++++---- ...recate-unit-duration-48b76c957bac5691.yaml | 25 ++++++++++++++++ test/utils/base.py | 22 ++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/deprecate-unit-duration-48b76c957bac5691.yaml diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 557d7df21ee..cbb0ebd19fa 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -341,6 +341,7 @@ def add_decomposition(self, decomposition): sel.add_equivalence(self, decomposition) @property + @deprecate_func(since="1.3.0", removal_timeline="in Qiskit 2.0.0", is_property=True) def duration(self): """Get the duration.""" return self._duration @@ -351,6 +352,7 @@ def duration(self, duration): self._duration = duration @property + @deprecate_func(since="1.3.0", removal_timeline="in Qiskit 2.0.0", is_property=True) def unit(self): """Get the time unit of duration.""" return self._unit diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 780efba5faf..0a151a8476e 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1145,17 +1145,35 @@ def __init__( for var, initial in declarations: self.add_var(var, initial) - self.duration: int | float | None = None - """The total duration of the circuit, set by a scheduling transpiler pass. Its unit is - specified by :attr:`unit`.""" - self.unit = "dt" - """The unit that :attr:`duration` is specified in.""" + self._duration = None + self._unit = "dt" self.metadata = {} if metadata is None else metadata """Arbitrary user-defined metadata for the circuit. - + Qiskit will not examine the content of this mapping, but it will pass it through the transpiler and reattach it to the output, so you can track your own metadata.""" + @property + @deprecate_func(since="1.3.0", removal_timeline="in Qiskit 2.0.0", is_property=True) + def duration(self): + """The total duration of the circuit, set by a scheduling transpiler pass. Its unit is + specified by :attr:`unit`.""" + return self._duration + + @duration.setter + def duration(self, value: int | float | None): + self._duration = value + + @property + @deprecate_func(since="1.3.0", removal_timeline="in Qiskit 2.0.0", is_property=True) + def unit(self): + """The unit that :attr:`duration` is specified in.""" + return self._unit + + @unit.setter + def unit(self, value): + self._unit = value + @classmethod def _from_circuit_data(cls, data: CircuitData, add_regs: bool = False) -> typing.Self: """A private constructor from rust space circuit data.""" diff --git a/releasenotes/notes/deprecate-unit-duration-48b76c957bac5691.yaml b/releasenotes/notes/deprecate-unit-duration-48b76c957bac5691.yaml new file mode 100644 index 00000000000..15c205c5a85 --- /dev/null +++ b/releasenotes/notes/deprecate-unit-duration-48b76c957bac5691.yaml @@ -0,0 +1,25 @@ +--- +deprecations_circuits: + - | + The :attr:`.QuantumCircuit.unit` and :attr:`.QuantumCircuit.duration` + attributes have been deprecated and will be removed in Qiskit 2.0.0. These + attributes were used to track the estimated duration and unit of that + duration to execute on the circuit. However, the values of these attributes + were always limited, as they would only be properly populated if the + transpiler were run with the correct settings. The duration was also only a + guess based on the longest path on the sum of the duration of + :class:`.DAGCircuit` and wouldn't ever correctly account for control flow + or conditionals in the circuit. + - | + The :attr:`.Instruction.duration` and :attr:`.Instruction.unit` attributes + have been deprecated and will be removed in Qiskit 2.0.0. These attributes + were used to attach a custom execution duration and unit for that duration + to an individual instruction. However, the source of truth of the duration + of a gate is the :class:`.BackendV2` :class:`.Target` which contains + the duration for each instruction supported on the backend. The duration of + an instruction is not something that's typically user adjustable and is + an immutable property of the backend. If you were previously using this + capability to experiment with different durations for gates you can + mutate the :attr:`.InstructionProperties.duration` field in a given + :class:`.Target` to set a custom duration for an instruction on a backend + (the unit is always in seconds in the :class:`.Target`). diff --git a/test/utils/base.py b/test/utils/base.py index 0038f7772eb..7645aca04ee 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -156,6 +156,28 @@ def setUpClass(cls): module="qiskit_aer", ) + # Remove these four filters in Qiskit 2.0.0 when we remove unit and duration + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=r".*The property.*qiskit.circuit.instruction.Instruction.unit.*", + ) + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=r".*The property.*qiskit.circuit.instruction.Instruction.duration.*", + ) + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=r".*The property.*qiskit.circuit.quantumcircuit.QuantumCircuit.unit.*", + ) + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=r".*The property.*qiskit.circuit.quantumcircuit.QuantumCircuit.duration.*", + ) + # Safe to remove once `FakeBackend` is removed (2.0) warnings.filterwarnings( "ignore", # If "default", it floods the CI output From a0c3026bc8ad011b85220138c74968cfdc5431f4 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Sep 2024 07:49:56 -0400 Subject: [PATCH 07/16] Fully port BarrierBeforeFinalMeasurements to rust (#13220) * Fully port BarrierBeforeFinalMeasurements to rust This commit migrates the BarrierBeforeFinalMeasurements transpiler pass to operate fully in Rust. The full path of the transpiler pass now never leaves Rust until it has finished modifying the DAGCircuit. The one exception is when the `label` is not set then we still call `MergeAdjacentBarriers` in the python code of the pass. This is the first step in the improvement of the performance of this pass. We can easily leverage multhreading to potentially parallelize the analysis portion of this pass that searches for the final operations and returns the set of indices. But this is blocked on #13219 which prevents us from accessing the PackedInstructions stored in the DAGCircuit in a multithreaded context. This commit also fixes an issue related to shared references in the disjoint_utils module around barrier labels. The combine_barriers() function was incorrectly mutating the label by reference which wouldn't persist in the DAG, and this was causing failures after the barrier was originally generated in Rust with this pass now. Fixes #12253 * Remove unused imports --- .../src/barrier_before_final_measurement.rs | 118 ++++++++++++++++++ crates/accelerate/src/lib.rs | 1 + crates/circuit/src/dag_circuit.rs | 2 +- crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 3 + .../passes/layout/disjoint_utils.py | 28 +++-- .../barrier_before_final_measurements.py | 58 +-------- 7 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 crates/accelerate/src/barrier_before_final_measurement.rs diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs new file mode 100644 index 00000000000..f7143d910b9 --- /dev/null +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -0,0 +1,118 @@ +// 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::prelude::*; +use rustworkx_core::petgraph::stable_graph::NodeIndex; + +use qiskit_circuit::circuit_instruction::ExtraInstructionAttributes; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::imports::BARRIER; +use qiskit_circuit::operations::{Operation, PyInstruction}; +use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; +use qiskit_circuit::Qubit; + +static FINAL_OP_NAMES: [&str; 2] = ["measure", "barrier"]; + +#[pyfunction] +pub fn barrier_before_final_measurements( + py: Python, + dag: &mut DAGCircuit, + label: Option, +) -> PyResult<()> { + let final_ops: HashSet = dag + .op_nodes(true) + .filter(|node| { + let NodeType::Operation(ref inst) = dag.dag()[*node] else { + unreachable!(); + }; + if !FINAL_OP_NAMES.contains(&inst.op.name()) { + return false; + } + let is_final_op = dag.bfs_successors(*node).all(|(_, child_successors)| { + !child_successors.iter().any(|suc| match dag.dag()[*suc] { + NodeType::Operation(ref suc_inst) => { + !FINAL_OP_NAMES.contains(&suc_inst.op.name()) + } + _ => false, + }) + }); + is_final_op + }) + .collect(); + if final_ops.is_empty() { + return Ok(()); + } + let ordered_node_indices: Vec = dag + .topological_op_nodes()? + .filter(|node| final_ops.contains(node)) + .collect(); + let final_packed_ops: Vec = ordered_node_indices + .into_iter() + .map(|node| { + let NodeType::Operation(ref inst) = dag.dag()[node] else { + unreachable!() + }; + let res = inst.clone(); + dag.remove_op_node(node); + res + }) + .collect(); + let new_barrier = BARRIER + .get_bound(py) + .call1((dag.num_qubits(), label.as_deref()))?; + + let new_barrier_py_inst = PyInstruction { + qubits: dag.num_qubits() as u32, + clbits: 0, + params: 0, + op_name: "barrier".to_string(), + control_flow: false, + #[cfg(feature = "cache_pygates")] + instruction: new_barrier.clone().unbind(), + #[cfg(not(feature = "cache_pygates"))] + instruction: new_barrier.unbind(), + }; + let qargs: Vec = (0..dag.num_qubits() as u32).map(Qubit).collect(); + #[cfg(feature = "cache_pygates")] + { + dag.apply_operation_back( + py, + PackedOperation::from_instruction(Box::new(new_barrier_py_inst)), + qargs.as_slice(), + &[], + None, + ExtraInstructionAttributes::new(label, None, None, None), + Some(new_barrier.unbind()), + )?; + } + #[cfg(not(feature = "cache_pygates"))] + { + dag.apply_operation_back( + py, + PackedOperation::from_instruction(Box::new(new_barrier_py_inst)), + qargs.as_slice(), + &[], + None, + ExtraInstructionAttributes::new(label, None, None, None), + )?; + } + for inst in final_packed_ops { + dag.push_back(py, inst)?; + } + Ok(()) +} + +pub fn barrier_before_final_measurements_mod(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(barrier_before_final_measurements))?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index f17fea5bccf..a0a2bfcdcaa 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 barrier_before_final_measurement; pub mod check_map; pub mod circuit_library; pub mod commutation_analysis; diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 54f270b3055..31cc67d25e0 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5074,7 +5074,7 @@ impl DAGCircuit { /// This is mostly used to apply operations from one DAG to /// another that was created from the first via /// [DAGCircuit::copy_empty_like]. - fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { + pub fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { let op_name = instr.op.name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &instr) { diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 8b54c535db2..dfda80623d6 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -28,6 +28,7 @@ where #[rustfmt::skip] #[pymodule] fn _accelerate(m: &Bound) -> PyResult<()> { + add_submodule(m, ::qiskit_accelerate::barrier_before_final_measurement::barrier_before_final_measurements_mod, "barrier_before_final_measurement")?; add_submodule(m, ::qiskit_accelerate::check_map::check_map_mod, "check_map")?; add_submodule(m, ::qiskit_accelerate::circuit_library::circuit_library, "circuit_library")?; add_submodule(m, ::qiskit_accelerate::commutation_analysis::commutation_analysis, "commutation_analysis")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 0e9694217cd..e96045e6801 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -83,6 +83,9 @@ sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford +sys.modules["qiskit._accelerate.barrier_before_final_measurement"] = ( + _accelerate.barrier_before_final_measurement +) sys.modules["qiskit._accelerate.commutation_checker"] = _accelerate.commutation_checker sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis sys.modules["qiskit._accelerate.commutation_cancellation"] = _accelerate.commutation_cancellation diff --git a/qiskit/transpiler/passes/layout/disjoint_utils.py b/qiskit/transpiler/passes/layout/disjoint_utils.py index 42f665c473a..3ecdc8137ce 100644 --- a/qiskit/transpiler/passes/layout/disjoint_utils.py +++ b/qiskit/transpiler/passes/layout/disjoint_utils.py @@ -107,7 +107,7 @@ def split_barriers(dag: DAGCircuit): num_qubits = len(node.qargs) if num_qubits == 1: continue - if node.op.label: + if node.label: barrier_uuid = f"{node.op.label}_uuid={uuid.uuid4()}" else: barrier_uuid = f"_none_uuid={uuid.uuid4()}" @@ -125,28 +125,30 @@ def split_barriers(dag: DAGCircuit): def combine_barriers(dag: DAGCircuit, retain_uuid: bool = True): """Mutate input dag to combine barriers with UUID labels into a single barrier.""" qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - uuid_map: dict[uuid.UUID, DAGOpNode] = {} + uuid_map: dict[str, DAGOpNode] = {} for node in dag.op_nodes(Barrier): - if node.op.label: - if "_uuid=" in node.op.label: - barrier_uuid = node.op.label + if node.label: + if "_uuid=" in node.label: + barrier_uuid = node.label else: continue if barrier_uuid in uuid_map: other_node = uuid_map[barrier_uuid] num_qubits = len(other_node.qargs) + len(node.qargs) - new_op = Barrier(num_qubits, label=barrier_uuid) + if not retain_uuid: + if isinstance(node.label, str) and node.label.startswith("_none_uuid="): + label = None + elif isinstance(node.label, str) and "_uuid=" in node.label: + label = "_uuid=".join(node.label.split("_uuid=")[:-1]) + else: + label = barrier_uuid + else: + label = barrier_uuid + new_op = Barrier(num_qubits, label=label) new_node = dag.replace_block_with_op([node, other_node], new_op, qubit_indices) uuid_map[barrier_uuid] = new_node else: uuid_map[barrier_uuid] = node - if not retain_uuid: - for node in dag.op_nodes(Barrier): - if isinstance(node.op.label, str) and node.op.label.startswith("_none_uuid="): - node.op.label = None - elif isinstance(node.op.label, str) and "_uuid=" in node.op.label: - original_label = "_uuid=".join(node.op.label.split("_uuid=")[:-1]) - node.op.label = original_label def require_layout_isolated_to_component( diff --git a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py index 4633cc57af5..c9270193f84 100644 --- a/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py +++ b/qiskit/transpiler/passes/utils/barrier_before_final_measurements.py @@ -13,9 +13,8 @@ """Add a barrier before final measurements.""" -from qiskit.circuit.barrier import Barrier +from qiskit._accelerate.barrier_before_final_measurement import barrier_before_final_measurements from qiskit.transpiler.basepasses import TransformationPass -from qiskit.dagcircuit import DAGCircuit, DAGOpNode from .merge_adjacent_barriers import MergeAdjacentBarriers @@ -33,60 +32,7 @@ def __init__(self, label=None): def run(self, dag): """Run the BarrierBeforeFinalMeasurements pass on `dag`.""" - # Collect DAG nodes which are followed only by barriers or other measures. - final_op_types = ["measure", "barrier"] - final_ops = [] - for candidate_node in dag.named_nodes(*final_op_types): - is_final_op = True - - for _, child_successors in dag.bfs_successors(candidate_node): - - if any( - isinstance(suc, DAGOpNode) and suc.name not in final_op_types - for suc in child_successors - ): - is_final_op = False - break - - if is_final_op: - final_ops.append(candidate_node) - - if not final_ops: - return dag - - # Create a layer with the barrier and add both bits and registers from the original dag. - barrier_layer = DAGCircuit() - barrier_layer.add_qubits(dag.qubits) - for qreg in dag.qregs.values(): - barrier_layer.add_qreg(qreg) - barrier_layer.add_clbits(dag.clbits) - for creg in dag.cregs.values(): - barrier_layer.add_creg(creg) - - # Add a barrier across all qubits so swap mapper doesn't add a swap - # from an unmeasured qubit after a measure. - final_qubits = dag.qubits - - barrier_layer.apply_operation_back( - Barrier(len(final_qubits), label=self.label), final_qubits, (), check=False - ) - - # Preserve order of final ops collected earlier from the original DAG. - ordered_final_nodes = [ - node for node in dag.topological_op_nodes() if node in set(final_ops) - ] - - # Move final ops to the new layer and append the new layer to the DAG. - for final_node in ordered_final_nodes: - barrier_layer.apply_operation_back( - final_node.op, final_node.qargs, final_node.cargs, check=False - ) - - for final_op in final_ops: - dag.remove_op_node(final_op) - - dag.compose(barrier_layer) - + barrier_before_final_measurements(dag, self.label) if self.label is None: # Merge the new barrier into any other barriers adjacent_pass = MergeAdjacentBarriers() From 8ccbc8d28f34e3f2e1e0e8cbb66c4da95754b341 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 26 Sep 2024 17:25:48 -0400 Subject: [PATCH 08/16] Fully port `GatesInBasis` to Rust. (#13034) * Fully port gates_in_basis to Rust. * Revert visibility to no longer public. * Add docstring for unwrap_operation. * Remove unused py token. --- crates/accelerate/src/gates_in_basis.rs | 117 ++++++++++++++++++ crates/accelerate/src/lib.rs | 1 + crates/circuit/src/dag_circuit.rs | 13 ++ crates/circuit/src/lib.rs | 2 +- crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/transpiler/passes/utils/gates_basis.py | 37 ++---- 7 files changed, 141 insertions(+), 31 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 00000000000..69f656c724c --- /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 a0a2bfcdcaa..aa477608298 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 31cc67d25e0..dbe8883791f 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 dcff558ade6..12351dcbbc7 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 dfda80623d6..458d9cc2bde 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 e96045e6801..359095f7a2e 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 16a68c3e533..56a80c1f228 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 1686815ddc502e7ac70a4e64303ec2cd4b598176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jofre=20Vall=C3=A8s=20Muns?= <61060572+jofrevalles@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:26:37 +0200 Subject: [PATCH 09/16] Fix typos in R gates (#13226) --- qiskit/circuit/library/standard_gates/rxx.py | 2 +- qiskit/circuit/library/standard_gates/ryy.py | 2 +- qiskit/circuit/library/standard_gates/rzx.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py index 3b069aa933b..887ea0d1540 100644 --- a/qiskit/circuit/library/standard_gates/rxx.py +++ b/qiskit/circuit/library/standard_gates/rxx.py @@ -63,7 +63,7 @@ class RXXGate(Gate): .. math:: - R_{XX}(\theta = \pi) = i X \otimes X + R_{XX}(\theta = \pi) = -i X \otimes X .. math:: diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py index ad185e88d04..94b40cd0aad 100644 --- a/qiskit/circuit/library/standard_gates/ryy.py +++ b/qiskit/circuit/library/standard_gates/ryy.py @@ -63,7 +63,7 @@ class RYYGate(Gate): .. math:: - R_{YY}(\theta = \pi) = i Y \otimes Y + R_{YY}(\theta = \pi) = -i Y \otimes Y .. math:: diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py index 003805cc6b5..279a9d12067 100644 --- a/qiskit/circuit/library/standard_gates/rzx.py +++ b/qiskit/circuit/library/standard_gates/rzx.py @@ -108,7 +108,7 @@ class RZXGate(Gate): .. math:: - R_{ZX}(\theta = \pi) = -i Z \otimes X + R_{ZX}(\theta = \pi) = -i X \otimes Z .. math:: From 4e6a874ac7d2b4736e8e87546f023b5592fc059c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 27 Sep 2024 14:45:22 -0400 Subject: [PATCH 10/16] Update contributing guide with more details on Rust process (#13170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update contributing guide with more details on Rust process As we're using an increasing amount of rust in Qiskit (as of this commit the repository is roughly 17% Rust by lines of code) the CONTRIBUTING.md file was a bit out of date as the guidance on working with Rust was a bit lacking. As an increasing number of code contributors to Qiskit will likely be writing some Rust code updating the guidance to be a bit more clear will be helpful. This commit goes through and expands on some details of working with and contributing Rust code. It also updates some other small details in the guide which were out of date. * Apply suggestions from code review Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Kevin Hartman --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Kevin Hartman --- CONTRIBUTING.md | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e196a0fe733..f00b725b2fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,8 +53,6 @@ from system-wide packages. This way, we avoid inadvertently becoming dependent o particular system configuration. For developers, this also makes it easy to maintain multiple environments (e.g. one per supported Python version, for older versions of Qiskit, etc.). - - ### Set up a Python venv All Python versions supported by Qiskit include built-in virtual environment module @@ -107,17 +105,17 @@ pip install -e . Qiskit is primarily written in Python but there are some core routines that are written in the [Rust](https://www.rust-lang.org/) programming language to improve the runtime performance. For the released versions of -qiskit we publish precompiled binaries on the +Qiskit we publish precompiled binaries on the [Python Package Index](https://pypi.org/) for all the supported platforms which only requires a functional Python environment to install. However, when -building and installing from source you will need a rust compiler installed. You can do this very easily +building and installing from source you will need a Rust compiler installed. You can do this very easily using rustup: https://rustup.rs/ which provides a single tool to install and configure the latest version of the rust compiler. [Other installation methods](https://forge.rust-lang.org/infra/other-installation-methods.html) exist too. For Windows users, besides rustup, you will also need install the Visual C++ build tools so that Rust can link against the system c/c++ libraries. You can see more details on this in the -[rustup documentation](https://rust-lang.github.io/rustup/installation/windows.html). +[rustup documentation](https://rust-lang.github.io/rustup/installation/windows-msvc.html). If you use Rustup, it will automatically install the correct Rust version currently used by the project. @@ -145,7 +143,7 @@ Python gate objects when accessing them from a `QuantumCircuit` or `DAGCircuit`. This makes a tradeoff between runtime performance for Python access and memory overhead. Caching gates will result in better runtime for users of Python at the cost of increased memory consumption. If you're working with any custom -transpiler passes written in python or are otherwise using a workflow that +transpiler passes written in Python or are otherwise using a workflow that repeatedly accesses the `operation` attribute of a `CircuitInstruction` or `op` attribute of `DAGOpNode` enabling caching is recommended. @@ -187,8 +185,8 @@ please ensure that: which will run these checks and report any issues. If your code fails the local style checks (specifically the black - code formatting check) you can use `tox -eblack` to automatically - fix update the code formatting. + or Rust code formatting check) you can use `tox -eblack` and + `cargo fmt` to automatically fix the code formatting. 2. The documentation has been updated accordingly. In particular, if a function or class has been modified during the PR, please update the *docstring* accordingly. @@ -396,11 +394,6 @@ it has been tagged: reno report --version 0.9.0 -At release time ``reno report`` is used to generate the release notes for the -release and the output will be submitted as a pull request to the documentation -repository's [release notes file]( -https://github.com/Qiskit/qiskit/blob/master/docs/release_notes.rst) - #### Building release notes locally Building The release notes are part of the standard qiskit documentation @@ -572,10 +565,11 @@ Note: If you have run `test/ipynb/mpl_tester.ipynb` locally it is possible some ### Testing Rust components -Rust-accelerated functions are generally tested from Python space, but in cases -where there is Rust-specific internal details to be tested, `#[test]` functions -can be included inline. Typically it's most convenient to place these in a -separate inline module that is only conditionally compiled in, such as +Many Rust-accelerated functions are generally tested from Python space, but in cases +where new Rust-native APIs are being added, or there are Rust-specific internal details +to be tested, `#[test]` functions can be included inline. It's typically most +convenient to place these in a separate inline module that is only conditionally +compiled in, such as ```rust #[cfg(test)] @@ -587,6 +581,9 @@ mod tests { } ``` +For more detailed guidance on how to add Rust testing you can refer to the Rust +documentation's [guide on writing tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html). + To run the Rust-space tests, do ```bash @@ -652,6 +649,15 @@ rather than via `tox`. If you have installed the development packages in your py `pip install -r requirements-dev.txt`, then `ruff` and `black` will be available and can be run from the command line. See [`tox.ini`](tox.ini) for how `tox` invokes them. +### Rust style and lint + +For formatting and lint checking Rust code, you'll need to use different tools than you would for Python. Qiskit uses [rustfmt](https://github.com/rust-lang/rustfmt) for +code formatting. You can simply run `cargo fmt` (if you installed Rust with the +default settings using `rustup`), and it will update the code formatting automatically to +conform to the style guidelines. This is very similar to running `tox -eblack` for Python code. For lint checking, Qiskit uses [clippy](https://github.com/rust-lang/rust-clippy) which can be invoked via `cargo clippy`. + +Rust lint and formatting checks are included in the the `tox -elint` command. For CI to pass you will need both checks to pass without any warnings or errors. Note that this command checks the code but won't apply any modifications, if you need to update formatting, you'll need to run `cargo fmt`. + ## Building API docs locally @@ -733,7 +739,7 @@ developers to test the release ahead of time. When the pre-release is tagged the automation will publish the pre-release to PyPI (but only get installed on user request), create the `stable/*` branch, and generate a pre-release changelog/release page. At this point the `main` opens up for development of the next release. The `stable/*` -branches should only receive changes in the form of bug fixes at this point. If there +branches should only receive changes in the form of bug fixes at this point. If there is a need additional release candidates can be published from `stable/*` and when the release is ready a full release will be tagged and published from `stable/*`. From 43feab335bce54133854b82225eb2795bc1d5d39 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:49:43 -0400 Subject: [PATCH 11/16] [Oxidize BasisTranslator] Add rust-native `compose_transforms()` (#13137) * Initial: Add `compose_transforms` and `get_example_gates` to Rust. * Add: `compose_transform` logic in rust * Fix: Correct the behavior of `compose_transforms`. - Use `PackedOperation.control_flow` instead of `CONTROL_FLOW_OP_NAMES` to check if an instructuion is a control flow operation. - Remove panic statement when checking for example gates. - Use qreg when adding an instruction to the mock_dag in `compose_transforms`. - Add missing comparison check in for loop to compare the mapped instructions. - Use borrowed `DAGCircuit` instances in the recursion of `get_example_gates`, do not use extract. - Fix behavior of `get_example_gates` to return `PackedInstruction` instances instead of `NodeIndex`. * Fix: Leverage rust-based `circuit_to_dag` converters. - Use `circuit_to_dag` and `DAGCircuit::from_circuit_data` to convert `QuantumCircuit` instances. * Format: Fix indentation of import lines in `compose_transforms.rs` * Formatting: Separate complicated type aliases * Fix: Adapt to new `DAGCircuit` limitations * Format: Remove unused imports in BasisTranslator * Fix: Adapt to #13143 * Fix: Code review comments - Remove unused `circuit_to_dag` and `OperationRef` imports. - Streamline complex types into simpler aliases. - Rename `CircuitRep` to `CircuitFromPython`. - Reshape `get_example_gates` to work with `CircuitData` instances during its recursion. - Remove unnecessary clone of `gate_name` in `compose_transform`. - Use `mapped_instructions` values when iterating in `compose_transforms`. - Rename `DAGCircuit::substitute_node_with_dag` to `DAGCircuit::py_*` in rust. - Adapted `_basis_search` to return a tuple of tuples instead of a 4-tuple. * Fix: More commments from code review - Remove stale comment related to #3947 - Rename tuple instances to `GateIdentifier`. - Rename `doomed_nodes` to `nodes_to_replace`. - Add docstring for `get_example_gates`. - Rename `get_example_gates` to `get_gate_num_params`. Co-authored-by: Kevin Hartman * Refactor: Rename `example_gates` to `gate_param_counts` - Remove `CircuitFromPython` and re-use original instance from `EquivalenceLibrary` after #12585 merged. --------- Co-authored-by: Kevin Hartman --- .../basis_translator/compose_transforms.rs | 192 ++++++++++++++++++ .../src/basis/basis_translator/mod.rs | 21 ++ crates/accelerate/src/basis/mod.rs | 21 ++ crates/accelerate/src/equivalence.rs | 39 ++-- crates/accelerate/src/lib.rs | 1 + crates/circuit/src/dag_circuit.rs | 8 +- crates/circuit/src/imports.rs | 2 + crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 2 + .../passes/basis/basis_translator.py | 109 +--------- 10 files changed, 276 insertions(+), 120 deletions(-) create mode 100644 crates/accelerate/src/basis/basis_translator/compose_transforms.rs create mode 100644 crates/accelerate/src/basis/basis_translator/mod.rs create mode 100644 crates/accelerate/src/basis/mod.rs diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs new file mode 100644 index 00000000000..e4498af0f8a --- /dev/null +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -0,0 +1,192 @@ +// 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_instruction::OperationFromPython; +use qiskit_circuit::imports::{GATE, PARAMETER_VECTOR, QUANTUM_REGISTER}; +use qiskit_circuit::parameter_table::ParameterUuid; +use qiskit_circuit::Qubit; +use qiskit_circuit::{ + circuit_data::CircuitData, + dag_circuit::{DAGCircuit, NodeType}, + operations::{Operation, Param}, +}; +use smallvec::SmallVec; + +use crate::equivalence::CircuitFromPython; + +// Custom types +pub type GateIdentifier = (String, u32); +pub type BasisTransformIn = (SmallVec<[Param; 3]>, CircuitFromPython); +pub type BasisTransformOut = (SmallVec<[Param; 3]>, DAGCircuit); + +#[pyfunction(name = "compose_transforms")] +pub(super) fn py_compose_transforms( + py: Python, + basis_transforms: Vec<(GateIdentifier, BasisTransformIn)>, + source_basis: HashSet, + source_dag: &DAGCircuit, +) -> PyResult> { + compose_transforms(py, &basis_transforms, &source_basis, source_dag) +} + +pub(super) fn compose_transforms<'a>( + py: Python, + basis_transforms: &'a [(GateIdentifier, BasisTransformIn)], + source_basis: &'a HashSet, + source_dag: &'a DAGCircuit, +) -> PyResult> { + let mut gate_param_counts: HashMap = HashMap::default(); + get_gates_num_params(source_dag, &mut gate_param_counts)?; + let mut mapped_instructions: HashMap = HashMap::new(); + + for (gate_name, gate_num_qubits) in source_basis.iter().cloned() { + let num_params = gate_param_counts[&(gate_name.clone(), gate_num_qubits)]; + + let placeholder_params: SmallVec<[Param; 3]> = PARAMETER_VECTOR + .get_bound(py) + .call1((&gate_name, num_params))? + .extract()?; + + let mut dag = DAGCircuit::new(py)?; + // Create the mock gate and add to the circuit, use Python for this. + let qubits = QUANTUM_REGISTER.get_bound(py).call1((gate_num_qubits,))?; + dag.add_qreg(py, &qubits)?; + + let gate = GATE.get_bound(py).call1(( + &gate_name, + gate_num_qubits, + placeholder_params + .iter() + .map(|x| x.clone_ref(py)) + .collect::>(), + ))?; + let gate_obj: OperationFromPython = gate.extract()?; + let qubits: Vec = (0..dag.num_qubits() as u32).map(Qubit).collect(); + dag.apply_operation_back( + py, + gate_obj.operation, + &qubits, + &[], + if gate_obj.params.is_empty() { + None + } else { + Some(gate_obj.params) + }, + gate_obj.extra_attrs, + #[cfg(feature = "cache_pygates")] + Some(gate.into()), + )?; + mapped_instructions.insert((gate_name, gate_num_qubits), (placeholder_params, dag)); + + for ((gate_name, gate_num_qubits), (equiv_params, equiv)) in basis_transforms { + for (_, dag) in &mut mapped_instructions.values_mut() { + let nodes_to_replace = dag + .op_nodes(true) + .filter_map(|node| { + if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) { + if (gate_name.as_str(), *gate_num_qubits) + == (op.op.name(), op.op.num_qubits()) + { + Some(( + node, + op.params_view() + .iter() + .map(|x| x.clone_ref(py)) + .collect::>(), + )) + } else { + None + } + } else { + None + } + }) + .collect::>(); + for (node, params) in nodes_to_replace { + let param_mapping: HashMap = equiv_params + .iter() + .map(|x| ParameterUuid::from_parameter(x.to_object(py).bind(py))) + .zip(params) + .map(|(uuid, param)| -> PyResult<(ParameterUuid, Param)> { + Ok((uuid?, param.clone_ref(py))) + }) + .collect::>()?; + let mut replacement = equiv.clone(); + replacement + .0 + .assign_parameters_from_mapping(py, param_mapping)?; + let replace_dag: DAGCircuit = + DAGCircuit::from_circuit_data(py, replacement.0, true)?; + let op_node = dag.get_node(py, node)?; + dag.py_substitute_node_with_dag( + py, + op_node.bind(py), + &replace_dag, + None, + true, + )?; + } + } + } + } + Ok(mapped_instructions) +} + +/// `DAGCircuit` variant. +/// +/// Gets the identifier of a gate instance (name, number of qubits) mapped to the +/// number of parameters it contains currently. +fn get_gates_num_params( + dag: &DAGCircuit, + example_gates: &mut HashMap, +) -> PyResult<()> { + for node in dag.op_nodes(true) { + if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) { + example_gates.insert( + (op.op.name().to_string(), op.op.num_qubits()), + op.params_view().len(), + ); + if op.op.control_flow() { + let blocks = op.op.blocks(); + for block in blocks { + get_gates_num_params_circuit(&block, example_gates)?; + } + } + } + } + Ok(()) +} + +/// `CircuitData` variant. +/// +/// Gets the identifier of a gate instance (name, number of qubits) mapped to the +/// number of parameters it contains currently. +fn get_gates_num_params_circuit( + circuit: &CircuitData, + example_gates: &mut HashMap, +) -> PyResult<()> { + for inst in circuit.iter() { + example_gates.insert( + (inst.op.name().to_string(), inst.op.num_qubits()), + inst.params_view().len(), + ); + if inst.op.control_flow() { + let blocks = inst.op.blocks(); + for block in blocks { + get_gates_num_params_circuit(&block, example_gates)?; + } + } + } + Ok(()) +} diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs new file mode 100644 index 00000000000..b97f4e37c4b --- /dev/null +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -0,0 +1,21 @@ +// 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 pyo3::prelude::*; + +mod compose_transforms; + +#[pymodule] +pub fn basis_translator(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(compose_transforms::py_compose_transforms))?; + Ok(()) +} diff --git a/crates/accelerate/src/basis/mod.rs b/crates/accelerate/src/basis/mod.rs new file mode 100644 index 00000000000..b072f7848fc --- /dev/null +++ b/crates/accelerate/src/basis/mod.rs @@ -0,0 +1,21 @@ +// 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 pyo3::{prelude::*, wrap_pymodule}; + +pub mod basis_translator; + +#[pymodule] +pub fn basis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(basis_translator::basis_translator))?; + Ok(()) +} diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs index e389151b6b0..55e1b0336ae 100644 --- a/crates/accelerate/src/equivalence.rs +++ b/crates/accelerate/src/equivalence.rs @@ -130,14 +130,14 @@ pub struct Equivalence { #[pyo3(get)] pub params: SmallVec<[Param; 3]>, #[pyo3(get)] - pub circuit: CircuitRep, + pub circuit: CircuitFromPython, } #[pymethods] impl Equivalence { #[new] #[pyo3(signature = (params, circuit))] - fn new(params: SmallVec<[Param; 3]>, circuit: CircuitRep) -> Self { + fn new(params: SmallVec<[Param; 3]>, circuit: CircuitFromPython) -> Self { Self { circuit, params } } @@ -295,15 +295,17 @@ impl<'py> FromPyObject<'py> for GateOper { } } -/// Representation of QuantumCircuit by using an instance of `CircuitData`.] +/// Used to extract an instance of [CircuitData] from a `QuantumCircuit`. +/// It also ensures seamless conversion back to `QuantumCircuit` once sent +/// back to Python. /// /// 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`. +/// of [CircuitData]. #[derive(Debug, Clone)] -pub struct CircuitRep(pub CircuitData); +pub struct CircuitFromPython(pub CircuitData); -impl FromPyObject<'_> for CircuitRep { +impl FromPyObject<'_> for CircuitFromPython { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { if ob.is_instance(QUANTUM_CIRCUIT.get_bound(ob.py()))? { let data: Bound = ob.getattr("_data")?; @@ -318,7 +320,7 @@ impl FromPyObject<'_> for CircuitRep { } } -impl IntoPy for CircuitRep { +impl IntoPy for CircuitFromPython { fn into_py(self, py: Python<'_>) -> PyObject { QUANTUM_CIRCUIT .get_bound(py) @@ -328,7 +330,7 @@ impl IntoPy for CircuitRep { } } -impl ToPyObject for CircuitRep { +impl ToPyObject for CircuitFromPython { fn to_object(&self, py: Python<'_>) -> PyObject { self.clone().into_py(py) } @@ -395,7 +397,7 @@ impl EquivalenceLibrary { &mut self, py: Python, gate: GateOper, - equivalent_circuit: CircuitRep, + equivalent_circuit: CircuitFromPython, ) -> PyResult<()> { self.add_equivalence(py, &gate.operation, &gate.params, equivalent_circuit) } @@ -425,7 +427,12 @@ impl EquivalenceLibrary { /// 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<()> { + fn py_set_entry( + &mut self, + py: Python, + gate: GateOper, + entry: Vec, + ) -> PyResult<()> { self.set_entry(py, &gate.operation, &gate.params, entry) } @@ -567,7 +574,7 @@ impl EquivalenceLibrary { py: Python, gate: &PackedOperation, params: &[Param], - equivalent_circuit: CircuitRep, + equivalent_circuit: CircuitFromPython, ) -> PyResult<()> { raise_if_shape_mismatch(gate, &equivalent_circuit)?; raise_if_param_mismatch(py, params, equivalent_circuit.0.unsorted_parameters(py)?)?; @@ -614,7 +621,7 @@ impl EquivalenceLibrary { py: Python, gate: &PackedOperation, params: &[Param], - entry: Vec, + entry: Vec, ) -> PyResult<()> { for equiv in entry.iter() { raise_if_shape_mismatch(gate, equiv)?; @@ -714,7 +721,7 @@ fn raise_if_param_mismatch( Ok(()) } -fn raise_if_shape_mismatch(gate: &PackedOperation, circuit: &CircuitRep) -> PyResult<()> { +fn raise_if_shape_mismatch(gate: &PackedOperation, circuit: &CircuitFromPython) -> 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 @@ -732,7 +739,11 @@ fn raise_if_shape_mismatch(gate: &PackedOperation, circuit: &CircuitRep) -> PyRe Ok(()) } -fn rebind_equiv(py: Python, equiv: Equivalence, query_params: &[Param]) -> PyResult { +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() diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index aa477608298..7c347c0a3d7 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -15,6 +15,7 @@ use std::env; use pyo3::import_exception; pub mod barrier_before_final_measurement; +pub mod basis; pub mod check_map; pub mod circuit_library; pub mod commutation_analysis; diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index dbe8883791f..693141279f7 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1001,7 +1001,7 @@ def _format(operand): } /// Add all wires in a quantum register. - fn add_qreg(&mut self, py: Python, qreg: &Bound) -> PyResult<()> { + pub fn add_qreg(&mut self, py: Python, qreg: &Bound) -> PyResult<()> { if !qreg.is_instance(imports::QUANTUM_REGISTER.get_bound(py))? { return Err(DAGCircuitError::new_err("not a QuantumRegister instance.")); } @@ -1684,7 +1684,7 @@ def _format(operand): /// Raises: /// DAGCircuitError: if a leaf node is connected to multiple outputs #[pyo3(name = "apply_operation_back", signature = (op, qargs=None, cargs=None, *, check=true))] - fn py_apply_operation_back( + pub fn py_apply_operation_back( &mut self, py: Python, op: Bound, @@ -2873,8 +2873,8 @@ def _format(operand): /// /// Raises: /// DAGCircuitError: if met with unexpected predecessor/successors - #[pyo3(signature = (node, input_dag, wires=None, propagate_condition=true))] - fn substitute_node_with_dag( + #[pyo3(name = "substitute_node_with_dag", signature = (node, input_dag, wires=None, propagate_condition=true))] + pub fn py_substitute_node_with_dag( &mut self, py: Python, node: &Bound, diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 5c77f8ef7d1..6e87bcb4f10 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -72,6 +72,8 @@ pub static CLASSICAL_REGISTER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.classicalregister", "ClassicalRegister"); pub static PARAMETER_EXPRESSION: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression"); +pub static PARAMETER_VECTOR: ImportOnceCell = + ImportOnceCell::new("qiskit.circuit.parametervector", "ParameterVector"); pub static QUANTUM_CIRCUIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit"); pub static SINGLETON_GATE: ImportOnceCell = diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 458d9cc2bde..2e0929543f1 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -29,6 +29,7 @@ where #[pymodule] fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::barrier_before_final_measurement::barrier_before_final_measurements_mod, "barrier_before_final_measurement")?; + add_submodule(m, ::qiskit_accelerate::basis::basis, "basis")?; add_submodule(m, ::qiskit_accelerate::check_map::check_map_mod, "check_map")?; add_submodule(m, ::qiskit_accelerate::circuit_library::circuit_library, "circuit_library")?; add_submodule(m, ::qiskit_accelerate::commutation_analysis::commutation_analysis, "commutation_analysis")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 359095f7a2e..1566db0be2c 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -53,6 +53,8 @@ # and not have to rely on attribute access. No action needed for top-level extension packages. sys.modules["qiskit._accelerate.circuit"] = _accelerate.circuit sys.modules["qiskit._accelerate.circuit_library"] = _accelerate.circuit_library +sys.modules["qiskit._accelerate.basis"] = _accelerate.basis +sys.modules["qiskit._accelerate.basis.basis_translator"] = _accelerate.basis.basis_translator 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 diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 0fdae973e1b..01cbd18c0ee 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -18,15 +18,11 @@ import logging from functools import singledispatchmethod -from itertools import zip_longest from collections import defaultdict import rustworkx from qiskit.circuit import ( - Gate, - ParameterVector, - QuantumRegister, ControlFlowOp, QuantumCircuit, ParameterExpression, @@ -37,6 +33,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit._accelerate.basis.basis_translator import compose_transforms logger = logging.getLogger(__name__) @@ -235,9 +232,9 @@ def run(self, dag): # Compose found path into a set of instruction substitution rules. compose_start_time = time.time() - instr_map = _compose_transforms(basis_transforms, source_basis, dag) + instr_map = compose_transforms(basis_transforms, source_basis, dag) extra_instr_map = { - qarg: _compose_transforms(transforms, qargs_local_source_basis[qarg], dag) + qarg: compose_transforms(transforms, qargs_local_source_basis[qarg], dag) for qarg, transforms in qarg_local_basis_transforms.items() } @@ -486,7 +483,9 @@ def discover_vertex(self, v, score): rule.circuit, score, ) - self._basis_transforms.append((gate.name, gate.num_qubits, rule.params, rule.circuit)) + self._basis_transforms.append( + ((gate.name, gate.num_qubits), (rule.params, rule.circuit)) + ) # we can stop the search if we have found all gates in the original circuit. if not self._source_gates_remain: # if we start from source gates and apply `basis_transforms` in reverse order, we'll end @@ -596,104 +595,10 @@ def _basis_search(equiv_lib, source_basis, target_basis): rtn = vis.basis_transforms logger.debug("Transformation path:") - for gate_name, gate_num_qubits, params, equiv in rtn: + for (gate_name, gate_num_qubits), (params, equiv) in rtn: logger.debug("%s/%s => %s\n%s", gate_name, gate_num_qubits, params, equiv) finally: # Remove dummy node in order to return graph to original state graph.remove_node(dummy) return rtn - - -def _compose_transforms(basis_transforms, source_basis, source_dag): - """Compose a set of basis transforms into a set of replacements. - - Args: - basis_transforms (List[Tuple[gate_name, params, equiv]]): List of - transforms to compose. - source_basis (Set[Tuple[gate_name: str, gate_num_qubits: int]]): Names - of gates which need to be translated. - source_dag (DAGCircuit): DAG with example gates from source_basis. - (Used to determine num_params for gate in source_basis.) - - Returns: - Dict[gate_name, Tuple(params, dag)]: Dictionary mapping between each gate - in source_basis and a DAGCircuit instance to replace it. Gates in - source_basis but not affected by basis_transforms will be included - as a key mapping to itself. - """ - example_gates = _get_example_gates(source_dag) - mapped_instrs = {} - - for gate_name, gate_num_qubits in source_basis: - # Need to grab a gate instance to find num_qubits and num_params. - # Can be removed following https://github.com/Qiskit/qiskit-terra/pull/3947 . - example_gate = example_gates[gate_name, gate_num_qubits] - num_params = len(example_gate.params) - - placeholder_params = ParameterVector(gate_name, num_params) - placeholder_gate = Gate(gate_name, gate_num_qubits, list(placeholder_params)) - placeholder_gate.params = list(placeholder_params) - - dag = DAGCircuit() - qr = QuantumRegister(gate_num_qubits) - dag.add_qreg(qr) - dag.apply_operation_back(placeholder_gate, qr, (), check=False) - mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag - - for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms: - logger.debug( - "Composing transform step: %s/%s %s =>\n%s", - gate_name, - gate_num_qubits, - equiv_params, - equiv, - ) - - for mapped_instr_name, (dag_params, dag) in mapped_instrs.items(): - doomed_nodes = [ - node - for node in dag.op_nodes() - if (node.name, node.num_qubits) == (gate_name, gate_num_qubits) - ] - - if doomed_nodes and logger.isEnabledFor(logging.DEBUG): - - logger.debug( - "Updating transform for mapped instr %s %s from \n%s", - mapped_instr_name, - dag_params, - dag_to_circuit(dag, copy_operations=False), - ) - - for node in doomed_nodes: - - replacement = equiv.assign_parameters(dict(zip_longest(equiv_params, node.params))) - - replacement_dag = circuit_to_dag(replacement) - - dag.substitute_node_with_dag(node, replacement_dag) - - if doomed_nodes and logger.isEnabledFor(logging.DEBUG): - - logger.debug( - "Updated transform for mapped instr %s %s to\n%s", - mapped_instr_name, - dag_params, - dag_to_circuit(dag, copy_operations=False), - ) - - return mapped_instrs - - -def _get_example_gates(source_dag): - def recurse(dag, example_gates=None): - example_gates = example_gates or {} - for node in dag.op_nodes(): - example_gates[(node.name, node.num_qubits)] = node - if node.name in CONTROL_FLOW_OP_NAMES: - for block in node.op.blocks: - example_gates = recurse(circuit_to_dag(block), example_gates) - return example_gates - - return recurse(source_dag) From 9778462ad84f1c5bdcf72eb7780bbc77621be3a6 Mon Sep 17 00:00:00 2001 From: cameron davis Date: Fri, 27 Sep 2024 17:44:28 -0400 Subject: [PATCH 12/16] unwrapped DAGCircuit::collect_runs() to not return an Option (#13222) * changed collect runs to unwrap Option and route error to DAGCircuit * changes by @kevinhartman * updated py_collect_runs * update collect_runs calls and specified lifteime of return --------- Co-authored-by: Kevin Hartman --- crates/accelerate/src/inverse_cancellation.rs | 4 +- crates/circuit/src/dag_circuit.rs | 40 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/crates/accelerate/src/inverse_cancellation.rs b/crates/accelerate/src/inverse_cancellation.rs index 1cfcc6c8321..9664f52089d 100644 --- a/crates/accelerate/src/inverse_cancellation.rs +++ b/crates/accelerate/src/inverse_cancellation.rs @@ -59,7 +59,7 @@ fn run_on_self_inverse( } let mut collect_set: HashSet = HashSet::with_capacity(1); collect_set.insert(gate.operation.name().to_string()); - let gate_runs: Vec> = dag.collect_runs(collect_set).unwrap().collect(); + let gate_runs: Vec> = dag.collect_runs(collect_set).collect(); for gate_cancel_run in gate_runs { let mut partitions: Vec> = Vec::new(); let mut chunk: Vec = Vec::new(); @@ -128,7 +128,7 @@ fn run_on_inverse_pairs( .iter() .map(|x| x.operation.name().to_string()) .collect(); - let runs: Vec> = dag.collect_runs(names).unwrap().collect(); + let runs: Vec> = dag.collect_runs(names).collect(); for nodes in runs { let mut i = 0; while i < nodes.len() - 1 { diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 693141279f7..d2d945bb127 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -4388,27 +4388,18 @@ def _format(operand): for name in namelist.iter() { name_list_set.insert(name.extract::()?); } - match self.collect_runs(name_list_set) { - Some(runs) => { - let run_iter = runs.map(|node_indices| { - PyTuple::new_bound( - py, - node_indices - .into_iter() - .map(|node_index| self.get_node(py, node_index).unwrap()), - ) - .unbind() - }); - let out_set = PySet::empty_bound(py)?; - for run_tuple in run_iter { - out_set.add(run_tuple)?; - } - Ok(out_set.unbind()) - } - None => Err(PyRuntimeError::new_err( - "Invalid DAGCircuit, cycle encountered", - )), + + let out_set = PySet::empty_bound(py)?; + + for run in self.collect_runs(name_list_set) { + let run_tuple = PyTuple::new_bound( + py, + run.into_iter() + .map(|node_index| self.get_node(py, node_index).unwrap()), + ); + out_set.add(run_tuple)?; } + Ok(out_set.unbind()) } /// Return a set of non-conditional runs of 1q "op" nodes. @@ -4970,7 +4961,7 @@ impl DAGCircuit { pub fn collect_runs( &self, namelist: HashSet, - ) -> Option> + '_> { + ) -> impl Iterator> + '_ { let filter_fn = move |node_index: NodeIndex| -> Result { let node = &self.dag[node_index]; match node { @@ -4980,8 +4971,11 @@ impl DAGCircuit { _ => Ok(false), } }; - rustworkx_core::dag_algo::collect_runs(&self.dag, filter_fn) - .map(|node_iter| node_iter.map(|x| x.unwrap())) + + match rustworkx_core::dag_algo::collect_runs(&self.dag, filter_fn) { + Some(iter) => iter.map(|result| result.unwrap()), + None => panic!("invalid DAG: cycle(s) detected!"), + } } /// Return a set of non-conditional runs of 1q "op" nodes. From c5c9a3522de52823209fffe3122b54a6976db747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:11:22 +0000 Subject: [PATCH 13/16] Bump once_cell from 1.20.0 to 1.20.1 (#13244) Bumps [once_cell](https://github.com/matklad/once_cell) from 1.20.0 to 1.20.1. - [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md) - [Commits](https://github.com/matklad/once_cell/compare/v1.20.0...v1.20.1) --- updated-dependencies: - dependency-name: once_cell dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 7 +++++-- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3de6bc61961..42620305b1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -903,9 +903,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "oq3_lexer" diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index df8c2436a5c..80c2c5f6c34 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -25,7 +25,7 @@ itertools.workspace = true qiskit-circuit.workspace = true thiserror.workspace = true ndarray_einsum_beta = "0.7" -once_cell = "1.20.0" +once_cell = "1.20.1" [dependencies.smallvec] workspace = true From 0019beefa3b806691359283b449d1b34ff497fe6 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:08:20 -0400 Subject: [PATCH 14/16] Change Qiskit bot for docs team (#13245) --- qiskit_bot.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml index 9bf7e320afe..73422179c8c 100644 --- a/qiskit_bot.yaml +++ b/qiskit_bot.yaml @@ -28,6 +28,7 @@ notifications: "(?!.*pulse.*)\\bvisualization\\b": - "@enavarro51" "^docs/": - - "@javabster" - "@Eric-Arellano" + - "@abbycross" + - "@beckykd" always_notify: true From be96aecc3384e47b20067834de0f6fe5992c31c0 Mon Sep 17 00:00:00 2001 From: melechlapson Date: Mon, 30 Sep 2024 10:05:22 -0500 Subject: [PATCH 15/16] Added Deprecation warnings for CU1Gate, CU3Gate and MCU1Gate (#13115) * added deprecation warnings for CU1Gate and MCU1Gate * fixed file formatting * deprecate CU3Gate and fix deprecation warnings for CU1Gate and CU3Gate * fixed docstring inaccuracies Co-authored-by: Julien Gacon * Remove mcu mention in warning * Update mcp example Co-authored-by: Julien Gacon --------- Co-authored-by: Julien Gacon --- qiskit/circuit/library/standard_gates/u1.py | 34 ++++++++++++++++++++- qiskit/circuit/library/standard_gates/u3.py | 17 +++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index e9bbed871d1..65bbb633fb3 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -39,7 +39,7 @@ class U1Gate(Gate): .. code-block:: python circuit = QuantumCircuit(1) - circuit.p(lambda, 0) # or circuit.u(0, 0, lambda) + circuit.p(lambda, 0) # or circuit.u(0, 0, lambda, 0) @@ -177,6 +177,22 @@ class CU1Gate(ControlledGate): This is a diagonal and symmetric gate that induces a phase on the state of the target qubit, depending on the control state. + .. warning:: + + This gate is deprecated. Instead, the :class:`.CPhaseGate` should be used + + .. math:: + + CU1(\lambda) = CP(\lambda) + + .. code-block:: python + + circuit = QuantumCircuit(2) + circuit.cp(lambda, 0, 1) + + + + **Circuit symbol:** .. parsed-literal:: @@ -332,6 +348,22 @@ class MCU1Gate(ControlledGate): This is a diagonal and symmetric gate that induces a phase on the state of the target qubit, depending on the state of the control qubits. + .. warning:: + + This gate is deprecated. Instead, the following replacements should be used + + .. math:: + + MCU1(\lambda) = MCP(\lambda) + + .. code-block:: python + + circuit = QuantumCircuit(5) + circuit.mcp(lambda, list(range(4)), 4) + + + + **Circuit symbol:** .. parsed-literal:: diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index df229af7d81..4efccaaf92b 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -186,6 +186,23 @@ class CU3Gate(ControlledGate): It is restricted to 3 parameters, and so cannot cover generic two-qubit controlled gates). + .. warning:: + + This gate is deprecated. Instead, the :class:`.CUGate` should be used + + .. math:: + + CU3(\theta, \phi, \lambda) = CU(\theta, \phi, \lambda, 0) + + .. code-block:: python + + circuit = QuantumCircuit(2) + gamma = 0 + circuit.cu(theta, phi, lambda, gamma, 0, 1) + + + + **Circuit symbol:** .. parsed-literal:: From 5ef634439121e0acc3f67a876877bfcdd883af9e Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:41:07 +0300 Subject: [PATCH 16/16] Add some more gates to CollectCliffords pass (#13214) * add matrix_based option to CollectClifford pass Co-authored-by: Samantha Barron * add a test to collect matrix_based gates * add release notes * minor update following review --- .../passes/optimization/collect_cliffords.py | 22 ++++- ...-to-collect-clifford-af88dd8f7a2a4bf9.yaml | 8 ++ .../python/transpiler/test_clifford_passes.py | 96 +++++++++++++++++-- 3 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml diff --git a/qiskit/transpiler/passes/optimization/collect_cliffords.py b/qiskit/transpiler/passes/optimization/collect_cliffords.py index 40acd21c685..5ee75af9800 100644 --- a/qiskit/transpiler/passes/optimization/collect_cliffords.py +++ b/qiskit/transpiler/passes/optimization/collect_cliffords.py @@ -15,6 +15,7 @@ from functools import partial +from qiskit.exceptions import QiskitError from qiskit.transpiler.passes.optimization.collect_and_collapse import ( CollectAndCollapse, collect_using_filter_function, @@ -37,6 +38,7 @@ def __init__( min_block_size=2, split_layers=False, collect_from_back=False, + matrix_based=False, ): """CollectCliffords initializer. @@ -51,11 +53,13 @@ def __init__( over disjoint qubit subsets. collect_from_back (bool): specifies if blocks should be collected started from the end of the circuit. + matrix_based (bool): specifies whether to collect unitary gates + which are Clifford gates only for certain parameters (based on their unitary matrix). """ collect_function = partial( collect_using_filter_function, - filter_function=_is_clifford_gate, + filter_function=partial(_is_clifford_gate, matrix_based=matrix_based), split_blocks=split_blocks, min_block_size=min_block_size, split_layers=split_layers, @@ -77,9 +81,21 @@ def __init__( ) -def _is_clifford_gate(node): +def _is_clifford_gate(node, matrix_based=False): """Specifies whether a node holds a clifford gate.""" - return node.op.name in clifford_gate_names and getattr(node.op, "condition", None) is None + if getattr(node.op, "condition", None) is not None: + return False + if node.op.name in clifford_gate_names: + return True + + if not matrix_based: + return False + + try: + Clifford(node.op) + return True + except QiskitError: + return False def _collapse_to_clifford(circuit): diff --git a/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml b/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml new file mode 100644 index 00000000000..05de0af7052 --- /dev/null +++ b/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml @@ -0,0 +1,8 @@ +--- +features_transpiler: + - | + Add an argument ``matrix_based`` to the :class:`.CollectCliffords()` transpiler pass. + If the new parameter ``matrix_based=True``, the :class:`.CollectCliffords()` transpiler pass + can collect :class:`.RZGate(np.pi/2)` gates and other unitary gates that are :class:`.Clifford()` + gates for certain parameters. + diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index ff8be63ffbc..66a7fd15ad1 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -27,6 +27,39 @@ from qiskit.compiler.transpiler import transpile from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.circuit.library import ( + CPhaseGate, + CRXGate, + CRYGate, + CRZGate, + CXGate, + CYGate, + CZGate, + DCXGate, + ECRGate, + HGate, + IGate, + iSwapGate, + RXGate, + RYGate, + RZGate, + RXXGate, + RYYGate, + RZZGate, + RZXGate, + SGate, + SdgGate, + SXGate, + SXdgGate, + SwapGate, + UGate, + XGate, + XXMinusYYGate, + XXPlusYYGate, + YGate, + ZGate, +) + class TestCliffordPasses(QiskitTestCase): """Tests to verify correctness of transpiler passes and @@ -381,7 +414,7 @@ def test_collect_cliffords_multiple_blocks(self): qc.cx(0, 1) qc.sdg(2) qc.swap(2, 1) - qc.rx(np.pi / 2, 1) + qc.t(1) qc.cz(0, 1) qc.z(0) qc.y(1) @@ -389,7 +422,7 @@ def test_collect_cliffords_multiple_blocks(self): # We should end up with two Cliffords and one "rx" gate qct = PassManager(CollectCliffords()).run(qc) self.assertEqual(qct.size(), 3) - self.assertIn("rx", qct.count_ops().keys()) + self.assertIn("t", qct.count_ops().keys()) self.assertEqual(qct.count_ops()["clifford"], 2) self.assertIsInstance(qct.data[0].operation, Clifford) @@ -479,9 +512,9 @@ def test_collect_cliffords_options_multiple_blocks(self): qc.x(2) qc.cx(2, 0) - qc.rx(np.pi / 2, 0) - qc.rx(np.pi / 2, 1) - qc.rx(np.pi / 2, 2) + qc.rx(np.pi / 4, 0) + qc.rx(np.pi / 4, 1) + qc.rx(np.pi / 4, 2) qc.cz(0, 1) qc.z(0) @@ -524,7 +557,7 @@ def test_collect_from_back_corectness(self): qc.h(0) qc.x(1) qc.h(1) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.y(0) qc.h(1) @@ -541,7 +574,7 @@ def test_collect_from_back_as_expected(self): qc.h(0) qc.x(1) qc.h(1) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.y(0) qc.h(1) @@ -728,7 +761,7 @@ def test_collect_with_all_types(self): qc.cy(0, 1) # not a clifford gate (separating the circuit) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.append(pauli_gate2, [0, 2, 1]) qc.append(lf2, [2, 1, 0]) @@ -749,6 +782,53 @@ def test_collect_with_all_types(self): op2 = Operator(qct) self.assertTrue(op1.equiv(op2)) + def test_collect_all_clifford_gates(self): + """Assert that CollectClifford collects all basis gates + (including certain rotation gates with pi/2 angles)""" + gates_1q = [ + XGate(), + YGate(), + ZGate(), + IGate(), + HGate(), + SGate(), + SdgGate(), + SXGate(), + SXdgGate(), + RXGate(theta=np.pi / 2), + RYGate(theta=np.pi / 2), + RZGate(phi=np.pi / 2), + UGate(np.pi / 2, np.pi / 2, np.pi / 2), + ] + gates_2q = [ + CXGate(), + CYGate(), + CZGate(), + DCXGate(), + ECRGate(), + SwapGate(), + iSwapGate(), + CPhaseGate(theta=np.pi), + CRXGate(theta=np.pi), + CRYGate(theta=np.pi), + CRZGate(theta=np.pi), + RXXGate(theta=np.pi / 2), + RYYGate(theta=np.pi / 2), + RZZGate(theta=np.pi / 2), + RZXGate(theta=np.pi / 2), + XXMinusYYGate(theta=np.pi), + XXPlusYYGate(theta=-np.pi), + ] + + qc = QuantumCircuit(2) + for gate in gates_1q: + qc.append(gate, [0]) + for gate in gates_2q: + qc.append(gate, [0, 1]) + + qct = PassManager(CollectCliffords(matrix_based=True)).run(qc) + self.assertEqual(qct.count_ops()["clifford"], 1) + if __name__ == "__main__": unittest.main()