diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53043ffaa28..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 @@ -440,21 +433,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 @@ -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/*`. diff --git a/Cargo.lock b/Cargo.lock index 15f5a7833ca..42620305b1a 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", @@ -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" @@ -1633,18 +1636,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", 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/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d91523cd7cc..80c2c5f6c34 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,12 +20,12 @@ 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 ndarray_einsum_beta = "0.7" -once_cell = "1.20.0" +once_cell = "1.20.1" [dependencies.smallvec] workspace = true 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/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 new file mode 100644 index 00000000000..55e1b0336ae --- /dev/null +++ b/crates/accelerate/src/equivalence.rs @@ -0,0 +1,816 @@ +// 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: CircuitFromPython, +} + +#[pymethods] +impl Equivalence { + #[new] + #[pyo3(signature = (params, circuit))] + fn new(params: SmallVec<[Param; 3]>, circuit: CircuitFromPython) -> 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, + }) + } +} + +/// 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]. +#[derive(Debug, Clone)] +pub struct CircuitFromPython(pub CircuitData); + +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")?; + 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 CircuitFromPython { + fn into_py(self, py: Python<'_>) -> PyObject { + QUANTUM_CIRCUIT + .get_bound(py) + .call_method1("_from_circuit_data", (self.0,)) + .unwrap() + .unbind() + } +} + +impl ToPyObject for CircuitFromPython { + 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: CircuitFromPython, + ) -> 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: CircuitFromPython, + ) -> 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: &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 + { + 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/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/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/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 92b9ef4052e..0f157678320 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -14,6 +14,8 @@ 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; @@ -23,10 +25,12 @@ pub mod convert_2q_block_matrix; pub mod dense_layout; pub mod edge_collections; pub mod elide_permutations; +pub mod equivalence; 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/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/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 0d357b31ad9..63456332fbe 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), @@ -988,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.")); } @@ -1671,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, @@ -2860,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, @@ -4375,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. @@ -4957,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 { @@ -4967,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. @@ -5074,7 +5081,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/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/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 6a35c4e85e9..b59f3c8d8ed 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -28,6 +28,8 @@ 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::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")?; @@ -35,11 +37,13 @@ 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::elide_permutations::elide_permutations, "elide_permutations")?; 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/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/qiskit/__init__.py b/qiskit/__init__.py index abef4a31968..71cad3ca30f 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -53,10 +53,14 @@ # 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 +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"] = ( @@ -83,6 +87,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/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/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/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:: 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:: 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/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 diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 5737385af15..01cbd18c0ee 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -13,29 +13,27 @@ """Translates gates to a target basis using a given equivalence library.""" +import random import time 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, ) 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 +from qiskit._accelerate.basis.basis_translator import compose_transforms logger = logging.getLogger(__name__) @@ -234,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() } @@ -485,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 @@ -559,7 +559,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 +577,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( @@ -590,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) 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/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/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/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() 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 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 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/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/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)) 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/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() 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 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