diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index bdceb20c3008..b32a96c3b423 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -14,14 +14,15 @@ jobs: name: Miri runs-on: ubuntu-latest env: - RUSTUP_TOOLCHAIN: nightly + RUSTUP_TOOLCHAIN: nightly-2024-05-24 steps: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly + uses: dtolnay/rust-toolchain@master with: + toolchain: nightly-2024-05-24 components: miri - name: Prepare Miri diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d04e21a1696..08530adfd4f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,7 @@ on: branches: [ main, 'stable/*' ] pull_request: branches: [ main, 'stable/*' ] + merge_group: concurrency: group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }} diff --git a/CITATION.bib b/CITATION.bib index a00798d9baaa..deac2ef200e3 100644 --- a/CITATION.bib +++ b/CITATION.bib @@ -1,6 +1,9 @@ -@misc{Qiskit, - author = {{Qiskit contributors}}, - title = {Qiskit: An Open-source Framework for Quantum Computing}, - year = {2023}, - doi = {10.5281/zenodo.2573505} +@misc{qiskit2024, + title={Quantum computing with {Q}iskit}, + author={Javadi-Abhari, Ali and Treinish, Matthew and Krsulich, Kevin and Wood, Christopher J. and Lishman, Jake and Gacon, Julien and Martiel, Simon and Nation, Paul D. and Bishop, Lev S. and Cross, Andrew W. and Johnson, Blake R. and Gambetta, Jay M.}, + year={2024}, + doi={10.48550/arXiv.2405.08810}, + eprint={2405.08810}, + archivePrefix={arXiv}, + primaryClass={quant-ph} } diff --git a/Cargo.lock b/Cargo.lock index 84e0e0090663..cf8e4f365df9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,18 +281,18 @@ dependencies = [ [[package]] name = "equator" -version = "0.1.10" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b0a88aa91d0ad2b9684e4479aed31a17d3f9051bdbbc634bd2c01bc5a5eee8" +checksum = "c35da53b5a021d2484a7cc49b2ac7f2d840f8236a286f84202369bd338d761ea" dependencies = [ "equator-macro", ] [[package]] name = "equator-macro" -version = "0.1.9" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d08acb9849f7fb4401564f251be5a526829183a3645a90197dea8e786cf3ae" +checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", @@ -307,9 +307,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "faer" -version = "0.18.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e547492d9b55c4ea882584e691ed092228981e337d0c800bc721301d7e61e40a" +checksum = "91ef9e1a4098a9e3a03c47bc5061406c04820552d869fd0fcd92587d07b271f0" dependencies = [ "bytemuck", "coe-rs", @@ -321,6 +321,7 @@ dependencies = [ "libm", "matrixcompare", "matrixcompare-core", + "nano-gemm", "npyz", "num-complex", "num-traits", @@ -334,9 +335,9 @@ dependencies = [ [[package]] name = "faer-entity" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ea5c06233193392c614a46aa3bbe3de29c1404692c8053abd9c2765a1cd159" +checksum = "ab968a02be27be95de0f1ad0af901b865fa0866b6a9b553a6cc9cf7f19c2ce71" dependencies = [ "bytemuck", "coe-rs", @@ -349,9 +350,9 @@ dependencies = [ [[package]] name = "faer-ext" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f67e0c5be50b08c57b59f1cf78a1c8399f6816f4e1a2e0801470ff58dad23a3" +checksum = "4cf30f6ae73f372c0e0cf7556c44e50f1eee0a714d71396091613d68c43625c9" dependencies = [ "faer", "ndarray", @@ -366,9 +367,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "gemm" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" +checksum = "e400f2ffd14e7548356236c35dc39cad6666d833a852cb8a8f3f28029359bb03" dependencies = [ "dyn-stack", "gemm-c32", @@ -386,9 +387,9 @@ dependencies = [ [[package]] name = "gemm-c32" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" +checksum = "10dc4a6176c8452d60eac1a155b454c91c668f794151a303bf3c75ea2874812d" dependencies = [ "dyn-stack", "gemm-common", @@ -401,9 +402,9 @@ dependencies = [ [[package]] name = "gemm-c64" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" +checksum = "cc2032ce2c0bb150da0256338759a6fb01ca056f6dfe28c4d14af32d7f878f6f" dependencies = [ "dyn-stack", "gemm-common", @@ -416,9 +417,9 @@ dependencies = [ [[package]] name = "gemm-common" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" +checksum = "90fd234fc525939654f47b39325fd5f55e552ceceea9135f3aa8bdba61eabef6" dependencies = [ "bytemuck", "dyn-stack", @@ -436,9 +437,9 @@ dependencies = [ [[package]] name = "gemm-f16" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" +checksum = "3fc3652651f96a711d46b8833e1fac27a864be4bdfa81a374055f33ddd25c0c6" dependencies = [ "dyn-stack", "gemm-common", @@ -454,9 +455,9 @@ dependencies = [ [[package]] name = "gemm-f32" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" +checksum = "acbc51c44ae3defd207e6d9416afccb3c4af1e7cef5e4960e4c720ac4d6f998e" dependencies = [ "dyn-stack", "gemm-common", @@ -469,9 +470,9 @@ dependencies = [ [[package]] name = "gemm-f64" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" +checksum = "3f37fc86e325c2415a4d0cab8324a0c5371ec06fc7d2f9cb1636fcfc9536a8d8" dependencies = [ "dyn-stack", "gemm-common", @@ -605,9 +606,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -696,6 +697,76 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "nano-gemm" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f563548d38f390ef9893e4883ec38c1fb312f569e98d76bededdd91a3b41a043" +dependencies = [ + "equator", + "nano-gemm-c32", + "nano-gemm-c64", + "nano-gemm-codegen", + "nano-gemm-core", + "nano-gemm-f32", + "nano-gemm-f64", + "num-complex", +] + +[[package]] +name = "nano-gemm-c32" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40449e57a5713464c3a1208c4c3301c8d29ee1344711822cf022bc91373a91b" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", + "num-complex", +] + +[[package]] +name = "nano-gemm-c64" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743a6e6211358fba85d1009616751e4107da86f4c95b24e684ce85f25c25b3bf" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", + "num-complex", +] + +[[package]] +name = "nano-gemm-codegen" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963bf7c7110d55430169dc74c67096375491ed580cd2ef84842550ac72e781fa" + +[[package]] +name = "nano-gemm-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3fc4f83ae8861bad79dc3c016bd6b0220da5f9de302e07d3112d16efc24aa6" + +[[package]] +name = "nano-gemm-f32" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3681b7ce35658f79da94b7f62c60a005e29c373c7111ed070e3bf64546a8bb" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", +] + +[[package]] +name = "nano-gemm-f64" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc1e619ed04d801809e1f63e61b669d380c4119e8b0cdd6ed184c6b111f046d8" +dependencies = [ + "nano-gemm-codegen", + "nano-gemm-core", +] + [[package]] name = "ndarray" version = "0.15.6" @@ -724,23 +795,23 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "bytemuck", "num-traits", + "rand", ] [[package]] @@ -754,9 +825,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -990,9 +1061,9 @@ dependencies = [ [[package]] name = "pulp" -version = "0.18.10" +version = "0.18.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14989307e408d9f4245d4fda09a7b144a08114ba124e26cab60ab83dc98db10" +checksum = "0ec8d02258294f59e4e223b41ad7e81c874aa6b15bc4ced9ba3965826da0eed5" dependencies = [ "bytemuck", "libm", @@ -1103,7 +1174,7 @@ dependencies = [ "faer-ext", "hashbrown 0.14.5", "indexmap 2.2.6", - "itertools 0.12.1", + "itertools 0.13.0", "ndarray", "num-bigint", "num-complex", diff --git a/SECURITY.md b/SECURITY.md index 1a8e9cea671e..45e6a9d6f516 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,13 +15,13 @@ We provide more detail on [the release and support schedule of Qiskit in our doc ## Reporting a Vulnerability To report vulnerabilities, you can privately report a potential security issue -via the Github security vulnerabilities feature. This can be done here: +via the GitHub security vulnerabilities feature. This can be done here: https://github.com/Qiskit/qiskit/security/advisories Please do **not** open a public issue about a potential security vulnerability. -You can find more details on the security vulnerability feature in the Github +You can find more details on the security vulnerability feature in the GitHub documentation here: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index a43fdc6ff506..63be9ad90b44 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,8 +20,8 @@ num-traits = "0.2" num-complex = "0.4" num-bigint = "0.4" rustworkx-core = "0.14" -faer = "0.18.2" -itertools = "0.12.1" +faer = "0.19.0" +itertools = "0.13.0" qiskit-circuit.workspace = true [dependencies.smallvec] @@ -49,9 +49,9 @@ workspace = true features = ["rayon"] [dependencies.faer-ext] -version = "0.1.0" +version = "0.2.0" features = ["ndarray"] [dependencies.pulp] -version = "0.18.10" +version = "0.18.21" features = ["macro"] diff --git a/crates/accelerate/src/dense_layout.rs b/crates/accelerate/src/dense_layout.rs index 7cb54140761e..901a906d9c81 100644 --- a/crates/accelerate/src/dense_layout.rs +++ b/crates/accelerate/src/dense_layout.rs @@ -15,8 +15,8 @@ use ahash::RandomState; use hashbrown::{HashMap, HashSet}; use indexmap::IndexSet; use ndarray::prelude::*; +use numpy::IntoPyArray; use numpy::PyReadonlyArray2; -use numpy::ToPyArray; use rayon::prelude::*; use pyo3::prelude::*; @@ -108,10 +108,35 @@ pub fn best_subset( use_error: bool, symmetric_coupling_map: bool, error_matrix: PyReadonlyArray2, -) -> PyResult<(PyObject, PyObject, PyObject)> { +) -> (PyObject, PyObject, PyObject) { let coupling_adj_mat = coupling_adjacency.as_array(); - let coupling_shape = coupling_adj_mat.shape(); let err = error_matrix.as_array(); + let [rows, cols, best_map] = best_subset_inner( + num_qubits, + coupling_adj_mat, + num_meas, + num_cx, + use_error, + symmetric_coupling_map, + err, + ); + ( + rows.into_pyarray_bound(py).into(), + cols.into_pyarray_bound(py).into(), + best_map.into_pyarray_bound(py).into(), + ) +} + +pub fn best_subset_inner( + num_qubits: usize, + coupling_adj_mat: ArrayView2, + num_meas: usize, + num_cx: usize, + use_error: bool, + symmetric_coupling_map: bool, + err: ArrayView2, +) -> [Vec; 3] { + let coupling_shape = coupling_adj_mat.shape(); let avg_meas_err = err.diag().mean().unwrap(); let map_fn = |k| -> SubsetResult { @@ -216,11 +241,7 @@ pub fn best_subset( let rows: Vec = new_cmap.iter().map(|edge| edge[0]).collect(); let cols: Vec = new_cmap.iter().map(|edge| edge[1]).collect(); - Ok(( - rows.to_pyarray_bound(py).into(), - cols.to_pyarray_bound(py).into(), - best_map.to_pyarray_bound(py).into(), - )) + [rows, cols, best_map] } #[pymodule] diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs index 8d0761666bb6..a4e83358a7da 100644 --- a/crates/accelerate/src/isometry.rs +++ b/crates/accelerate/src/isometry.rs @@ -212,7 +212,6 @@ fn construct_basis_states( } else if i == target_label { e2 += 1; } else { - assert!(j <= 1); e1 += state_free[j] as usize; e2 += state_free[j] as usize; j += 1 diff --git a/crates/accelerate/src/sabre/layout.rs b/crates/accelerate/src/sabre/layout.rs index 1cb539d9598e..a1e5e9ce6418 100644 --- a/crates/accelerate/src/sabre/layout.rs +++ b/crates/accelerate/src/sabre/layout.rs @@ -15,6 +15,7 @@ use pyo3::prelude::*; use pyo3::Python; use hashbrown::HashSet; +use ndarray::prelude::*; use numpy::{IntoPyArray, PyArray, PyReadonlyArray2}; use rand::prelude::*; use rand_pcg::Pcg64Mcg; @@ -29,6 +30,8 @@ use super::sabre_dag::SabreDAG; use super::swap_map::SwapMap; use super::{Heuristic, NodeBlockResults, SabreResult}; +use crate::dense_layout::best_subset_inner; + #[pyfunction] #[pyo3(signature = (dag, neighbor_table, distance_matrix, heuristic, max_iterations, num_swap_trials, num_random_trials, seed=None, partial_layouts=vec![]))] pub fn sabre_layout_and_routing( @@ -52,6 +55,12 @@ pub fn sabre_layout_and_routing( let mut starting_layouts: Vec>> = (0..num_random_trials).map(|_| vec![]).collect(); starting_layouts.append(&mut partial_layouts); + // Run a dense layout trial + starting_layouts.push(compute_dense_starting_layout( + dag.num_qubits, + &target, + run_in_parallel, + )); let outer_rng = match seed { Some(seed) => Pcg64Mcg::seed_from_u64(seed), None => Pcg64Mcg::from_entropy(), @@ -208,3 +217,26 @@ fn layout_trial( .collect(); (initial_layout, final_permutation, sabre_result) } + +fn compute_dense_starting_layout( + num_qubits: usize, + target: &RoutingTargetView, + run_in_parallel: bool, +) -> Vec> { + let mut adj_matrix = target.distance.to_owned(); + if run_in_parallel { + adj_matrix.par_mapv_inplace(|x| if x == 1. { 1. } else { 0. }); + } else { + adj_matrix.mapv_inplace(|x| if x == 1. { 1. } else { 0. }); + } + let [_rows, _cols, map] = best_subset_inner( + num_qubits, + adj_matrix.view(), + 0, + 0, + false, + true, + aview2(&[[0.]]), + ); + map.into_iter().map(|x| Some(x as u32)).collect() +} diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index 5d6a82df7940..808269d8ab90 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -141,7 +141,9 @@ impl ZXPaulis { phases: &Bound>, coeffs: &Bound>, ) -> PyResult { - let &[num_ops, num_qubits] = x.shape() else { unreachable!("PyArray2 must be 2D") }; + let &[num_ops, num_qubits] = x.shape() else { + unreachable!("PyArray2 must be 2D") + }; if z.shape() != [num_ops, num_qubits] { return Err(PyValueError::new_err(format!( "'x' and 'z' have different shapes: {:?} and {:?}", diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 590fc07e8f8b..944565cf36d8 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -324,7 +324,7 @@ impl CircuitData { 0, )?; res.intern_context = self.intern_context.clone(); - res.data = self.data.clone(); + res.data.clone_from(&self.data); Ok(res) } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 48b0c4d20eee..86bd2e69c111 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -129,23 +129,7 @@ impl CircuitInstruction { ) } - fn __getstate__(&self, py: Python<'_>) -> PyObject { - ( - self.operation.bind(py), - self.qubits.bind(py), - self.clbits.bind(py), - ) - .into_py(py) - } - - fn __setstate__(&mut self, _py: Python<'_>, state: &Bound) -> PyResult<()> { - self.operation = state.get_item(0)?.extract()?; - self.qubits = state.get_item(1)?.extract()?; - self.clbits = state.get_item(2)?.extract()?; - Ok(()) - } - - pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult { + fn __getnewargs__(&self, py: Python<'_>) -> PyResult { Ok(( self.operation.bind(py), self.qubits.bind(py), diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs new file mode 100644 index 000000000000..c766461bb510 --- /dev/null +++ b/crates/circuit/src/dag_node.rs @@ -0,0 +1,283 @@ +// 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 crate::circuit_instruction::CircuitInstruction; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple}; +use pyo3::{intern, PyObject, PyResult}; + +/// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. +#[pyclass(module = "qiskit._accelerate.circuit", subclass)] +#[derive(Clone, Debug)] +pub struct DAGNode { + #[pyo3(get, set)] + pub _node_id: isize, +} + +#[pymethods] +impl DAGNode { + #[new] + #[pyo3(signature=(nid=-1))] + fn new(nid: isize) -> Self { + DAGNode { _node_id: nid } + } + + fn __getstate__(&self) -> isize { + self._node_id + } + + fn __setstate__(&mut self, nid: isize) { + self._node_id = nid; + } + + fn __lt__(&self, other: &DAGNode) -> bool { + self._node_id < other._node_id + } + + fn __gt__(&self, other: &DAGNode) -> bool { + self._node_id > other._node_id + } + + fn __str__(_self: &Bound) -> String { + format!("{}", _self.as_ptr() as usize) + } + + fn __hash__(&self, py: Python) -> PyResult { + self._node_id.into_py(py).bind(py).hash() + } +} + +/// Object to represent an Instruction at a node in the DAGCircuit. +#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)] +pub struct DAGOpNode { + pub instruction: CircuitInstruction, + #[pyo3(get)] + pub sort_key: PyObject, +} + +#[pymethods] +impl DAGOpNode { + #[new] + fn new( + py: Python, + op: PyObject, + qargs: Option<&Bound>, + cargs: Option<&Bound>, + dag: Option<&Bound>, + ) -> PyResult<(Self, DAGNode)> { + let qargs = + qargs.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; + let cargs = + cargs.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; + + let sort_key = match dag { + Some(dag) => { + let cache = dag + .getattr(intern!(py, "_key_cache"))? + .downcast_into_exact::()?; + let cache_key = PyTuple::new_bound(py, [&qargs, &cargs]); + match cache.get_item(&cache_key)? { + Some(key) => key, + None => { + let indices: PyResult> = qargs + .iter() + .chain(cargs.iter()) + .map(|bit| { + dag.call_method1(intern!(py, "find_bit"), (bit,))? + .getattr(intern!(py, "index")) + }) + .collect(); + let index_strs: Vec<_> = + indices?.into_iter().map(|i| format!("{:04}", i)).collect(); + let key = PyString::new_bound(py, index_strs.join(",").as_str()); + cache.set_item(&cache_key, &key)?; + key.into_any() + } + } + } + None => qargs.str()?.into_any(), + }; + + Ok(( + DAGOpNode { + instruction: CircuitInstruction { + operation: op, + qubits: qargs.unbind(), + clbits: cargs.unbind(), + }, + sort_key: sort_key.unbind(), + }, + DAGNode { _node_id: -1 }, + )) + } + + fn __reduce__(slf: PyRef, py: Python) -> PyObject { + let state = (slf.as_ref()._node_id, &slf.sort_key); + ( + py.get_type_bound::(), + ( + &slf.instruction.operation, + &slf.instruction.qubits, + &slf.instruction.clbits, + ), + state, + ) + .into_py(py) + } + + fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { + let (nid, sort_key): (isize, PyObject) = state.extract()?; + slf.as_mut()._node_id = nid; + slf.sort_key = sort_key; + Ok(()) + } + + #[getter] + fn get_op(&self, py: Python) -> PyObject { + self.instruction.operation.clone_ref(py) + } + + #[setter] + fn set_op(&mut self, op: PyObject) { + self.instruction.operation = op; + } + + #[getter] + fn get_qargs(&self, py: Python) -> Py { + self.instruction.qubits.clone_ref(py) + } + + #[setter] + fn set_qargs(&mut self, qargs: Py) { + self.instruction.qubits = qargs; + } + + #[getter] + fn get_cargs(&self, py: Python) -> Py { + self.instruction.clbits.clone_ref(py) + } + + #[setter] + fn set_cargs(&mut self, cargs: Py) { + self.instruction.clbits = cargs; + } + + /// Returns the Instruction name corresponding to the op for this node + #[getter] + fn get_name(&self, py: Python) -> PyResult { + Ok(self + .instruction + .operation + .bind(py) + .getattr(intern!(py, "name"))? + .unbind()) + } + + /// Sets the Instruction name corresponding to the op for this node + #[setter] + fn set_name(&self, py: Python, new_name: PyObject) -> PyResult<()> { + self.instruction + .operation + .bind(py) + .setattr(intern!(py, "name"), new_name) + } + + /// Returns a representation of the DAGOpNode + fn __repr__(&self, py: Python) -> PyResult { + Ok(format!( + "DAGOpNode(op={}, qargs={}, cargs={})", + self.instruction.operation.bind(py).repr()?, + self.instruction.qubits.bind(py).repr()?, + self.instruction.clbits.bind(py).repr()? + )) + } +} + +/// Object to represent an incoming wire node in the DAGCircuit. +#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)] +pub struct DAGInNode { + #[pyo3(get)] + wire: PyObject, + #[pyo3(get)] + sort_key: PyObject, +} + +#[pymethods] +impl DAGInNode { + #[new] + fn new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> { + Ok(( + DAGInNode { + wire, + sort_key: PyList::empty_bound(py).str()?.into_any().unbind(), + }, + DAGNode { _node_id: -1 }, + )) + } + + fn __reduce__(slf: PyRef, py: Python) -> PyObject { + let state = (slf.as_ref()._node_id, &slf.sort_key); + (py.get_type_bound::(), (&slf.wire,), state).into_py(py) + } + + fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { + let (nid, sort_key): (isize, PyObject) = state.extract()?; + slf.as_mut()._node_id = nid; + slf.sort_key = sort_key; + Ok(()) + } + + /// Returns a representation of the DAGInNode + fn __repr__(&self, py: Python) -> PyResult { + Ok(format!("DAGInNode(wire={})", self.wire.bind(py).repr()?)) + } +} + +/// Object to represent an outgoing wire node in the DAGCircuit. +#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)] +pub struct DAGOutNode { + #[pyo3(get)] + wire: PyObject, + #[pyo3(get)] + sort_key: PyObject, +} + +#[pymethods] +impl DAGOutNode { + #[new] + fn new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> { + Ok(( + DAGOutNode { + wire, + sort_key: PyList::empty_bound(py).str()?.into_any().unbind(), + }, + DAGNode { _node_id: -1 }, + )) + } + + fn __reduce__(slf: PyRef, py: Python) -> PyObject { + let state = (slf.as_ref()._node_id, &slf.sort_key); + (py.get_type_bound::(), (&slf.wire,), state).into_py(py) + } + + fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { + let (nid, sort_key): (isize, PyObject) = state.extract()?; + slf.as_mut()._node_id = nid; + slf.sort_key = sort_key; + Ok(()) + } + + /// Returns a representation of the DAGOutNode + fn __repr__(&self, py: Python) -> PyResult { + Ok(format!("DAGOutNode(wire={})", self.wire.bind(py).repr()?)) + } +} diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index cd560bad7387..c186c4243e93 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -12,6 +12,7 @@ pub mod circuit_data; pub mod circuit_instruction; +pub mod dag_node; pub mod intern_context; use pyo3::prelude::*; @@ -30,6 +31,10 @@ pub enum SliceOrInt<'a> { #[pymodule] pub fn circuit(m: Bound) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; Ok(()) } diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs index f7faad0c6298..d8a08080a95c 100644 --- a/crates/qasm2/src/expr.rs +++ b/crates/qasm2/src/expr.rs @@ -501,8 +501,13 @@ impl<'a> ExprParser<'a> { | TokenType::Sin | TokenType::Sqrt | TokenType::Tan => Ok(Some(Atom::Function(token.ttype.into()))), - TokenType::Real => Ok(Some(Atom::Const(token.real(self.context)))), - TokenType::Integer => Ok(Some(Atom::Const(token.int(self.context) as f64))), + // This deliberately parses an _integer_ token as a float, since all OpenQASM 2.0 + // integers can be interpreted as floats, and doing that allows us to gracefully handle + // cases where a huge float would overflow a `usize`. Never mind that in such a case, + // there's almost certainly precision loss from the floating-point representating + // having insufficient mantissa digits to faithfully represent the angle mod 2pi; + // that's not our fault in the parser. + TokenType::Real | TokenType::Integer => Ok(Some(Atom::Const(token.real(self.context)))), TokenType::Pi => Ok(Some(Atom::Const(f64::consts::PI))), TokenType::Id => { let id = token.text(self.context); @@ -698,6 +703,11 @@ impl<'a> ExprParser<'a> { /// Parse a single expression completely. This is the only public entry point to the /// operator-precedence parser. + /// + /// .. note:: + /// + /// This evaluates in a floating-point context, including evaluating integer tokens, since + /// the only places that expressions are valid in OpenQASM 2 is during gate applications. pub fn parse_expression(&mut self, cause: &Token) -> PyResult { self.eval_expression(0, cause) } diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs index 024681b877ff..f9f674cbc93e 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -262,9 +262,9 @@ impl Token { } /// If the token is a real number, this method can be called to evaluate its value. Panics if - /// the token is not a real number. + /// the token is not a float or an integer. pub fn real(&self, context: &TokenContext) -> f64 { - if self.ttype != TokenType::Real { + if !(self.ttype == TokenType::Real || self.ttype == TokenType::Integer) { panic!() } context.text[self.index].parse().unwrap() diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index 747980819a0e..330805fa2f86 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -16,7 +16,6 @@ use pyo3::types::{PyList, PyString, PyTuple, PyType}; use crate::error::QASM3ImporterError; pub trait PyRegister { - fn bit(&self, py: Python, index: usize) -> PyResult>; // This really should be // fn iter<'a>(&'a self, py: Python<'a>) -> impl Iterator; // or at a minimum @@ -39,15 +38,6 @@ macro_rules! register_type { } impl PyRegister for $name { - /// Get an individual bit from the register. - fn bit(&self, py: Python, index: usize) -> PyResult> { - // Unfortunately, `PyList::get_item_unchecked` isn't usable with the stable ABI. - self.items - .bind(py) - .get_item(index) - .map(|item| item.into_py(py)) - } - fn bit_list<'a>(&'a self, py: Python<'a>) -> &Bound<'a, PyList> { self.items.bind(py) } diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index 3a6c1b04cfdf..8581d56ace77 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -1,42 +1,90 @@ .. module:: qiskit +.. + Within each section, the modules should be ordered alphabetically by + module name (not RST filename). ============= API Reference ============= +Circuit construction: + .. toctree:: :maxdepth: 1 circuit - circuit_library + qiskit.circuit.QuantumCircuit circuit_classical - circuit_singleton - compiler - visualization classicalfunction + circuit_library + circuit_singleton + +Quantum information: + +.. toctree:: + :maxdepth: 1 + + quantum_info + +Transpilation: + +.. toctree:: + :maxdepth: 1 + converters - assembler dagcircuit passmanager + synthesis + qiskit.synthesis.unitary.aqc + transpiler + transpiler_passes + transpiler_synthesis_plugins + transpiler_preset + transpiler_plugins + +Primitives and providers: + +.. toctree:: + :maxdepth: 1 + + primitives providers providers_basic_provider providers_fake_provider providers_models - pulse - scheduler - synthesis - qiskit.synthesis.unitary.aqc - primitives + +Results and visualizations: + +.. toctree:: + :maxdepth: 1 + + result + visualization + +Serialization: + +.. toctree:: + :maxdepth: 1 + qasm2 qasm3 - qobj qpy - quantum_info - result - transpiler - transpiler_passes - transpiler_preset - transpiler_plugins - transpiler_synthesis_plugins - utils + +Pulse-level programming: + +.. toctree:: + :maxdepth: 1 + + pulse + scheduler + +Other: + +.. toctree:: + :maxdepth: 1 + + assembler + compiler exceptions + qobj + utils diff --git a/docs/apidoc/qiskit.circuit.QuantumCircuit.rst b/docs/apidoc/qiskit.circuit.QuantumCircuit.rst new file mode 100644 index 000000000000..1fa9cb5a7d9c --- /dev/null +++ b/docs/apidoc/qiskit.circuit.QuantumCircuit.rst @@ -0,0 +1,17 @@ +.. _qiskit-circuit-quantumcircuit: + +============================== +:class:`.QuantumCircuit` class +============================== + +.. + This is so big it gets its own page in the toctree, and because we + don't want it to use autosummary. + +.. currentmodule:: qiskit.circuit + +.. autoclass:: qiskit.circuit.QuantumCircuit + :no-members: + :no-inherited-members: + :no-special-members: + :class-doc-from: class diff --git a/pyproject.toml b/pyproject.toml index 9c7094827c8a..35a14a5524ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,20 +217,15 @@ disable = [ # remove from here and fix the issues. Else, move it above this section and add a comment # with the rationale "arguments-renamed", - "broad-exception-raised", - "consider-iterating-dictionary", - "consider-using-dict-items", "consider-using-enumerate", "consider-using-f-string", "no-member", "no-value-for-parameter", "not-context-manager", "unexpected-keyword-arg", - "unnecessary-dict-index-lookup", "unnecessary-dunder-call", "unnecessary-lambda-assignment", "unspecified-encoding", - "unsupported-assignment-operation", ] enable = [ diff --git a/qiskit/assembler/__init__.py b/qiskit/assembler/__init__.py index a356501a263c..45798084ea62 100644 --- a/qiskit/assembler/__init__.py +++ b/qiskit/assembler/__init__.py @@ -17,23 +17,18 @@ .. currentmodule:: qiskit.assembler -Circuit Assembler -================= +Functions +========= -.. autofunction:: assemble_circuits -Schedule Assembler -================== +.. autofunction:: assemble_circuits .. autofunction:: assemble_schedules -Disassembler -============ - .. autofunction:: disassemble -RunConfig -========= +Classes +======= .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 9fbefb4c5d9f..43087760153e 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -203,8 +203,8 @@ .. _circuit-module-api: -API overview of :mod:`qiskit.circuit` -===================================== +API overview of qiskit.circuit +============================== All objects here are described in more detail, and in their greater context in the following sections. This section provides an overview of the API elements documented here. @@ -321,16 +321,6 @@ :class:`QuantumCircuit` class itself and the multitude of available methods on it in its class documentation. -.. - TODO: the intention is to replace this `autosummary` directive with a proper entry in the API - toctree once the `QuantumCircuit` class-level documentation has been completely rewritten into - more of this style. For now, this just ensures it gets *any* page generated. - -.. autosummary:: - :toctree: ../stubs/ - - QuantumCircuit - Internally, a :class:`QuantumCircuit` contains the qubits, classical bits, compile-time parameters, real-time variables, and other tracking information about the data it acts on and how it is parametrized. It then contains a sequence of :class:`CircuitInstruction`\ s, which contain @@ -390,7 +380,7 @@ Circuits track registers, but registers themselves impart almost no behavioral differences on circuits. The only exception is that :class:`ClassicalRegister`\ s can be implicitly cast to unsigned integers for use in conditional comparisons of :ref:`control flow operations -`. +`. Classical registers and bits were the original way of representing classical data in Qiskit, and remain the most supported currently. Longer term, the data model is moving towards a more complete @@ -433,6 +423,8 @@ circuit), but these are now discouraged and you should use the alternatives noted in those methods. +.. _circuit-operations-instructions: + Operations, instructions and gates ---------------------------------- @@ -598,17 +590,14 @@ Real-time classical computation ------------------------------- -.. note:: +.. seealso:: + :mod:`qiskit.circuit.classical` + Module-level documentation for how the variable-, expression- and type-systems work, the + objects used to represent them, and the classical operations available. - The primary documentation for real-time classical computation is in the module-level - documentation of :mod:`qiskit.circuit.classical`. - - You might also want to read about the circuit methods for working with real-time variables on - the :class:`QuantumCircuit` class page. - - .. - TODO: write a section in the QuantumCircuit-level guide about real-time-variable methods and - cross-ref to it. + :ref:`circuit-real-time-methods` + The :class:`QuantumCircuit` methods for working with these variables in the context of a + single circuit. Qiskit has rudimentary low-level support for representing real-time classical computations, which happen during the QPU execution and affect the results. We are still relatively early into hardware @@ -674,7 +663,7 @@ ParameterVector -.. _circuit-control-flow: +.. _circuit-control-flow-repr: Control flow in circuits ------------------------ @@ -718,11 +707,8 @@ The classes representations are documented here, but please note that manually constructing these classes is a low-level operation that we do not expect users to need to do frequently. - .. - TODO: make this below statement valid, and reinsert. - - Users should read :ref:`circuit-creating-control-flow` for the recommended workflows for - building control-flow-enabled circuits. + Users should read :ref:`circuit-control-flow-methods` for the recommended workflows for building + control-flow-enabled circuits. Since :class:`ControlFlowOp` subclasses are also :class:`Instruction` subclasses, this means that the way they are stored in :class:`CircuitInstruction` instances has them "applied" to a sequence of @@ -772,11 +758,8 @@ argument), but user code will typically use the control-flow builder interface, which handles this automatically. -.. - TODO: make the below sentence valid, then re-insert. - - Consult :ref:`the control-flow construction documentation ` for - more information on how to build circuits with control flow. +Consult :ref:`the control-flow construction documentation ` for more +information on how to build circuits with control flow. .. _circuit-custom-gates: @@ -833,10 +816,11 @@ ``__array__``. This is used by :meth:`Gate.to_matrix`, and has the signature: .. currentmodule:: None -.. py:method:: __array__(dtype=None, copy=None) +.. py:method:: object.__array__(dtype=None, copy=None) - Return a Numpy array representing the gate. This can use the gate's :attr:`~Instruction.params` - field, and may assume that these are numeric values (assuming the subclass expects that) and not + Return a Numpy array representing the gate. This can use the gate's + :attr:`~qiskit.circuit.Instruction.params` field, and may assume that these are numeric + values (assuming the subclass expects that) and not :ref:`compile-time parameters `. For greatest efficiency, the returned array should default to a dtype of :class:`complex`. @@ -920,122 +904,6 @@ def __array__(self, dtype=None, copy=None): Working with circuit-level objects ================================== -Circuit properties ------------------- - -.. - TODO: rewrite this section and move it into the `QuantumCircuit` class-level overview of its - functions. - -When constructing quantum circuits, there are several properties that help quantify -the "size" of the circuits, and their ability to be run on a noisy quantum device. -Some of these, like number of qubits, are straightforward to understand, while others -like depth and number of tensor components require a bit more explanation. Here we will -explain all of these properties, and, in preparation for understanding how circuits change -when run on actual devices, highlight the conditions under which they change. - -Consider the following circuit: - -.. plot:: - :include-source: - - from qiskit import QuantumCircuit - qc = QuantumCircuit(12) - for idx in range(5): - qc.h(idx) - qc.cx(idx, idx+5) - - qc.cx(1, 7) - qc.x(8) - qc.cx(1, 9) - qc.x(7) - qc.cx(1, 11) - qc.swap(6, 11) - qc.swap(6, 9) - qc.swap(6, 10) - qc.x(6) - qc.draw('mpl') - -From the plot, it is easy to see that this circuit has 12 qubits, and a collection of -Hadamard, CNOT, X, and SWAP gates. But how to quantify this programmatically? Because we -can do single-qubit gates on all the qubits simultaneously, the number of qubits in this -circuit is equal to the **width** of the circuit: - -.. code-block:: - - qc.width() - -.. parsed-literal:: - - 12 - -We can also just get the number of qubits directly: - -.. code-block:: - - qc.num_qubits - -.. parsed-literal:: - - 12 - -.. important:: - - For a quantum circuit composed from just qubits, the circuit width is equal - to the number of qubits. This is the definition used in quantum computing. However, - for more complicated circuits with classical registers, and classically controlled gates, - this equivalence breaks down. As such, from now on we will not refer to the number of - qubits in a quantum circuit as the width. - - -It is also straightforward to get the number and type of the gates in a circuit using -:meth:`QuantumCircuit.count_ops`: - -.. code-block:: - - qc.count_ops() - -.. parsed-literal:: - - OrderedDict([('cx', 8), ('h', 5), ('x', 3), ('swap', 3)]) - -We can also get just the raw count of operations by computing the circuits -:meth:`QuantumCircuit.size`: - -.. code-block:: - - qc.size() - -.. parsed-literal:: - - 19 - -A particularly important circuit property is known as the circuit **depth**. The depth -of a quantum circuit is a measure of how many "layers" of quantum gates, executed in -parallel, it takes to complete the computation defined by the circuit. Because quantum -gates take time to implement, the depth of a circuit roughly corresponds to the amount of -time it takes the quantum computer to execute the circuit. Thus, the depth of a circuit -is one important quantity used to measure if a quantum circuit can be run on a device. - -The depth of a quantum circuit has a mathematical definition as the longest path in a -directed acyclic graph (DAG). However, such a definition is a bit hard to grasp, even for -experts. Fortunately, the depth of a circuit can be easily understood by anyone familiar -with playing `Tetris `_. Lets see how to compute this -graphically: - -.. image:: /source_images/depth.gif - - -We can verify our graphical result using :meth:`QuantumCircuit.depth`: - -.. code-block:: - - qc.depth() - -.. parsed-literal:: - - 9 - .. _circuit-abstract-to-physical: Converting abstract circuits to physical circuits diff --git a/qiskit/circuit/_classical_resource_map.py b/qiskit/circuit/_classical_resource_map.py index 454826d6035d..bff7d9f80fec 100644 --- a/qiskit/circuit/_classical_resource_map.py +++ b/qiskit/circuit/_classical_resource_map.py @@ -143,3 +143,6 @@ def visit_binary(self, node, /): def visit_cast(self, node, /): return expr.Cast(node.operand.accept(self), node.type, implicit=node.implicit) + + def visit_index(self, node, /): + return expr.Index(node.target.accept(self), node.index.accept(self), node.type) diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py index c0057ca96f02..00f1c2e06767 100644 --- a/qiskit/circuit/classical/expr/__init__.py +++ b/qiskit/circuit/classical/expr/__init__.py @@ -43,7 +43,7 @@ real-time variable, or a wrapper around a :class:`.Clbit` or :class:`.ClassicalRegister`. .. autoclass:: Var - :members: var, name + :members: var, name, new Similarly, literals used in expressions (such as integers) should be lifted to :class:`Value` nodes with associated types. diff --git a/qiskit/circuit/classical/types/__init__.py b/qiskit/circuit/classical/types/__init__.py index 93ab90e32166..14365fd32a6f 100644 --- a/qiskit/circuit/classical/types/__init__.py +++ b/qiskit/circuit/classical/types/__init__.py @@ -47,13 +47,14 @@ Working with types ================== -There are some functions on these types exposed here as well. These are mostly expected to be used -only in manipulations of the expression tree; users who are building expressions using the +There are some additional functions on these types documented in the subsequent sections. +These are mostly expected to be used only in manipulations of the expression tree; +users who are building expressions using the :ref:`user-facing construction interface ` should not need to use these. Partial ordering of types -------------------------- +========================= The type system is equipped with a partial ordering, where :math:`a < b` is interpreted as ":math:`a` is a strict subtype of :math:`b`". Note that the partial ordering is a subset of the @@ -78,7 +79,7 @@ Casting between types ---------------------- +===================== It is common to need to cast values of one type to another type. The casting rules for this are embedded into the :mod:`types` module. You can query the casting kinds using :func:`cast_kind`: diff --git a/qiskit/circuit/classicalfunction/__init__.py b/qiskit/circuit/classicalfunction/__init__.py index a2268acfe2db..a072d910f97a 100644 --- a/qiskit/circuit/classicalfunction/__init__.py +++ b/qiskit/circuit/classicalfunction/__init__.py @@ -81,6 +81,7 @@ def grover_oracle(a: Int1, b: Int1, c: Int1, d: Int1) -> Int1: Decorator for a classical function that returns a `ClassicalFunction` object. +.. autofunction:: classical_function ClassicalFunction ----------------- diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 5f21967e4828..a9ae005d982d 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -129,35 +129,19 @@ Standard Directives =================== -.. - This summary table deliberately does not generate toctree entries; these directives are "owned" - by ``qiskit.circuit``. - Directives are operations to the quantum stack that are meant to be interpreted by the backend or the transpiler. In general, the transpiler or backend might optionally ignore them if there is no implementation for them. -.. - This summary table deliberately does not generate toctree entries; these directives are "owned" - by ``qiskit.circuit``. - -.. autosummary:: - - Barrier +* :class:`qiskit.circuit.Barrier` Standard Operations =================== Operations are non-reversible changes in the quantum state of the circuit. -.. - This summary table deliberately does not generate toctree entries; these directives are "owned" - by ``qiskit.circuit``. - -.. autosummary:: - - Measure - Reset +* :class:`qiskit.circuit.Measure` +* :class:`qiskit.circuit.Reset` Generalized Gates ================= diff --git a/qiskit/circuit/library/data_preparation/initializer.py b/qiskit/circuit/library/data_preparation/initializer.py index 394f863191d5..0e38f067403c 100644 --- a/qiskit/circuit/library/data_preparation/initializer.py +++ b/qiskit/circuit/library/data_preparation/initializer.py @@ -36,6 +36,14 @@ class Initialize(Instruction): the :class:`~.library.StatePreparation` class. Note that ``Initialize`` is an :class:`~.circuit.Instruction` and not a :class:`.Gate` since it contains a reset instruction, which is not unitary. + + The initial state is prepared based on the :class:`~.library.Isometry` synthesis described in [1]. + + References: + 1. Iten et al., Quantum circuits for isometries (2016). + `Phys. Rev. A 93, 032318 + `__. + """ def __init__( diff --git a/qiskit/circuit/library/data_preparation/pauli_feature_map.py b/qiskit/circuit/library/data_preparation/pauli_feature_map.py index b05287ca049f..03bbc031ec63 100644 --- a/qiskit/circuit/library/data_preparation/pauli_feature_map.py +++ b/qiskit/circuit/library/data_preparation/pauli_feature_map.py @@ -97,7 +97,7 @@ class PauliFeatureMap(NLocal): >>> from qiskit.circuit.library import EfficientSU2 >>> prep = PauliFeatureMap(3, reps=3, paulis=['Z', 'YY', 'ZXZ']) >>> wavefunction = EfficientSU2(3) - >>> classifier = prep.compose(wavefunction + >>> classifier = prep.compose(wavefunction) >>> classifier.num_parameters 27 >>> classifier.count_ops() diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py index 2d48e5cd0776..43e80ead8836 100644 --- a/qiskit/circuit/library/data_preparation/state_preparation.py +++ b/qiskit/circuit/library/data_preparation/state_preparation.py @@ -11,7 +11,6 @@ # that they have been altered from the originals. """Prepare a quantum state from the state where all qubits are 0.""" -import cmath from typing import Union, Optional import math @@ -21,11 +20,10 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.gate import Gate -from qiskit.circuit.library.standard_gates.x import CXGate, XGate +from qiskit.circuit.library.standard_gates.x import XGate from qiskit.circuit.library.standard_gates.h import HGate from qiskit.circuit.library.standard_gates.s import SGate, SdgGate -from qiskit.circuit.library.standard_gates.ry import RYGate -from qiskit.circuit.library.standard_gates.rz import RZGate +from qiskit.circuit.library.generalized_gates import Isometry from qiskit.circuit.exceptions import CircuitError from qiskit.quantum_info.states.statevector import Statevector # pylint: disable=cyclic-import @@ -71,13 +69,13 @@ def __init__( Raises: QiskitError: ``num_qubits`` parameter used when ``params`` is not an integer - When a Statevector argument is passed the state is prepared using a recursive - initialization algorithm, including optimizations, from [1], as well - as some additional optimizations including removing zero rotations and double cnots. + When a Statevector argument is passed the state is prepared based on the + :class:`~.library.Isometry` synthesis described in [1]. - **References:** - [1] Shende, Bullock, Markov. Synthesis of Quantum Logic Circuits (2004) - [`https://arxiv.org/abs/quant-ph/0406176v5`] + References: + 1. Iten et al., Quantum circuits for isometries (2016). + `Phys. Rev. A 93, 032318 + `__. """ self._params_arg = params @@ -119,7 +117,7 @@ def _define(self): elif self._from_int: self.definition = self._define_from_int() else: - self.definition = self._define_synthesis() + self.definition = self._define_synthesis_isom() def _define_from_label(self): q = QuantumRegister(self.num_qubits, "q") @@ -168,29 +166,18 @@ def _define_from_int(self): # we don't need to invert anything return initialize_circuit - def _define_synthesis(self): - """Calculate a subcircuit that implements this initialization - - Implements a recursive initialization algorithm, including optimizations, - from "Synthesis of Quantum Logic Circuits" Shende, Bullock, Markov - https://arxiv.org/abs/quant-ph/0406176v5 + def _define_synthesis_isom(self): + """Calculate a subcircuit that implements this initialization via isometry""" + q = QuantumRegister(self.num_qubits, "q") + initialize_circuit = QuantumCircuit(q, name="init_def") - Additionally implements some extra optimizations: remove zero rotations and - double cnots. - """ - # call to generate the circuit that takes the desired vector to zero - disentangling_circuit = self._gates_to_uncompute() + isom = Isometry(self._params_arg, 0, 0) + initialize_circuit.append(isom, q[:]) # invert the circuit to create the desired vector from zero (assuming # the qubits are in the zero state) - if self._inverse is False: - initialize_instr = disentangling_circuit.to_instruction().inverse() - else: - initialize_instr = disentangling_circuit.to_instruction() - - q = QuantumRegister(self.num_qubits, "q") - initialize_circuit = QuantumCircuit(q, name="init_def") - initialize_circuit.append(initialize_instr, q[:]) + if self._inverse is True: + return initialize_circuit.inverse() return initialize_circuit @@ -253,164 +240,3 @@ def validate_parameter(self, parameter): def _return_repeat(self, exponent: float) -> "Gate": return Gate(name=f"{self.name}*{exponent}", num_qubits=self.num_qubits, params=[]) - - def _gates_to_uncompute(self): - """Call to create a circuit with gates that take the desired vector to zero. - - Returns: - QuantumCircuit: circuit to take self.params vector to :math:`|{00\\ldots0}\\rangle` - """ - q = QuantumRegister(self.num_qubits) - circuit = QuantumCircuit(q, name="disentangler") - - # kick start the peeling loop, and disentangle one-by-one from LSB to MSB - remaining_param = self.params - - for i in range(self.num_qubits): - # work out which rotations must be done to disentangle the LSB - # qubit (we peel away one qubit at a time) - (remaining_param, thetas, phis) = StatePreparation._rotations_to_disentangle( - remaining_param - ) - - # perform the required rotations to decouple the LSB qubit (so that - # it can be "factored" out, leaving a shorter amplitude vector to peel away) - - add_last_cnot = True - if np.linalg.norm(phis) != 0 and np.linalg.norm(thetas) != 0: - add_last_cnot = False - - if np.linalg.norm(phis) != 0: - rz_mult = self._multiplex(RZGate, phis, last_cnot=add_last_cnot) - circuit.append(rz_mult.to_instruction(), q[i : self.num_qubits]) - - if np.linalg.norm(thetas) != 0: - ry_mult = self._multiplex(RYGate, thetas, last_cnot=add_last_cnot) - circuit.append(ry_mult.to_instruction().reverse_ops(), q[i : self.num_qubits]) - circuit.global_phase -= np.angle(sum(remaining_param)) - return circuit - - @staticmethod - def _rotations_to_disentangle(local_param): - """ - Static internal method to work out Ry and Rz rotation angles used - to disentangle the LSB qubit. - These rotations make up the block diagonal matrix U (i.e. multiplexor) - that disentangles the LSB. - - [[Ry(theta_1).Rz(phi_1) 0 . . 0], - [0 Ry(theta_2).Rz(phi_2) . 0], - . - . - 0 0 Ry(theta_2^n).Rz(phi_2^n)]] - """ - remaining_vector = [] - thetas = [] - phis = [] - - param_len = len(local_param) - - for i in range(param_len // 2): - # Ry and Rz rotations to move bloch vector from 0 to "imaginary" - # qubit - # (imagine a qubit state signified by the amplitudes at index 2*i - # and 2*(i+1), corresponding to the select qubits of the - # multiplexor being in state |i>) - (remains, add_theta, add_phi) = StatePreparation._bloch_angles( - local_param[2 * i : 2 * (i + 1)] - ) - - remaining_vector.append(remains) - - # rotations for all imaginary qubits of the full vector - # to move from where it is to zero, hence the negative sign - thetas.append(-add_theta) - phis.append(-add_phi) - - return remaining_vector, thetas, phis - - @staticmethod - def _bloch_angles(pair_of_complex): - """ - Static internal method to work out rotation to create the passed-in - qubit from the zero vector. - """ - [a_complex, b_complex] = pair_of_complex - # Force a and b to be complex, as otherwise numpy.angle might fail. - a_complex = complex(a_complex) - b_complex = complex(b_complex) - mag_a = abs(a_complex) - final_r = math.sqrt(mag_a**2 + abs(b_complex) ** 2) - if final_r < _EPS: - theta = 0 - phi = 0 - final_r = 0 - final_t = 0 - else: - theta = 2 * math.acos(mag_a / final_r) - a_arg = cmath.phase(a_complex) - b_arg = cmath.phase(b_complex) - final_t = a_arg + b_arg - phi = b_arg - a_arg - - return final_r * cmath.exp(1.0j * final_t / 2), theta, phi - - def _multiplex(self, target_gate, list_of_angles, last_cnot=True): - """ - Return a recursive implementation of a multiplexor circuit, - where each instruction itself has a decomposition based on - smaller multiplexors. - - The LSB is the multiplexor "data" and the other bits are multiplexor "select". - - Args: - target_gate (Gate): Ry or Rz gate to apply to target qubit, multiplexed - over all other "select" qubits - list_of_angles (list[float]): list of rotation angles to apply Ry and Rz - last_cnot (bool): add the last cnot if last_cnot = True - - Returns: - DAGCircuit: the circuit implementing the multiplexor's action - """ - list_len = len(list_of_angles) - local_num_qubits = int(math.log2(list_len)) + 1 - - q = QuantumRegister(local_num_qubits) - circuit = QuantumCircuit(q, name="multiplex" + str(local_num_qubits)) - - lsb = q[0] - msb = q[local_num_qubits - 1] - - # case of no multiplexing: base case for recursion - if local_num_qubits == 1: - circuit.append(target_gate(list_of_angles[0]), [q[0]]) - return circuit - - # calc angle weights, assuming recursion (that is the lower-level - # requested angles have been correctly implemented by recursion - angle_weight = np.kron([[0.5, 0.5], [0.5, -0.5]], np.identity(2 ** (local_num_qubits - 2))) - - # calc the combo angles - list_of_angles = angle_weight.dot(np.array(list_of_angles)).tolist() - - # recursive step on half the angles fulfilling the above assumption - multiplex_1 = self._multiplex(target_gate, list_of_angles[0 : (list_len // 2)], False) - circuit.append(multiplex_1.to_instruction(), q[0:-1]) - - # attach CNOT as follows, thereby flipping the LSB qubit - circuit.append(CXGate(), [msb, lsb]) - - # implement extra efficiency from the paper of cancelling adjacent - # CNOTs (by leaving out last CNOT and reversing (NOT inverting) the - # second lower-level multiplex) - multiplex_2 = self._multiplex(target_gate, list_of_angles[(list_len // 2) :], False) - if list_len > 1: - circuit.append(multiplex_2.to_instruction().reverse_ops(), q[0:-1]) - else: - circuit.append(multiplex_2.to_instruction(), q[0:-1]) - - # attach a final CNOT - if last_cnot: - circuit.append(CXGate(), [msb, lsb]) - - return circuit diff --git a/qiskit/circuit/library/generalized_gates/isometry.py b/qiskit/circuit/library/generalized_gates/isometry.py index c180e7a14484..e6b4f6fb21cc 100644 --- a/qiskit/circuit/library/generalized_gates/isometry.py +++ b/qiskit/circuit/library/generalized_gates/isometry.py @@ -45,10 +45,10 @@ class Isometry(Instruction): The decomposition is based on [1]. - **References:** - - [1] Iten et al., Quantum circuits for isometries (2016). - `Phys. Rev. A 93, 032318 `__. + References: + 1. Iten et al., Quantum circuits for isometries (2016). + `Phys. Rev. A 93, 032318 + `__. """ @@ -123,8 +123,8 @@ def _define(self): # later here instead. gate = self.inv_gate() gate = gate.inverse() - q = QuantumRegister(self.num_qubits) - iso_circuit = QuantumCircuit(q) + q = QuantumRegister(self.num_qubits, "q") + iso_circuit = QuantumCircuit(q, name="isometry") iso_circuit.append(gate, q[:]) self.definition = iso_circuit @@ -139,8 +139,8 @@ def _gates_to_uncompute(self): Call to create a circuit with gates that take the desired isometry to the first 2^m columns of the 2^n*2^n identity matrix (see https://arxiv.org/abs/1501.06911) """ - q = QuantumRegister(self.num_qubits) - circuit = QuantumCircuit(q) + q = QuantumRegister(self.num_qubits, "q") + circuit = QuantumCircuit(q, name="isometry_to_uncompute") ( q_input, q_ancillas_for_output, diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 68deaddd7327..519a306c357e 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2021. +# (C) Copyright IBM 2017, 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 @@ -16,7 +16,6 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate from qiskit.circuit.exceptions import CircuitError -from qiskit.synthesis.linear import check_invertible_binary_matrix from qiskit.circuit.library.generalized_gates.permutation import PermutationGate # pylint: disable=cyclic-import @@ -115,6 +114,8 @@ def __init__( # Optionally, check that the matrix is invertible if validate_input: + from qiskit.synthesis.linear import check_invertible_binary_matrix + if not check_invertible_binary_matrix(linear): raise CircuitError( "A linear function must be represented by an invertible matrix." diff --git a/qiskit/circuit/library/generalized_gates/mcg_up_to_diagonal.py b/qiskit/circuit/library/generalized_gates/mcg_up_to_diagonal.py index 49d7dc36958a..b95ec6f63e35 100644 --- a/qiskit/circuit/library/generalized_gates/mcg_up_to_diagonal.py +++ b/qiskit/circuit/library/generalized_gates/mcg_up_to_diagonal.py @@ -68,8 +68,8 @@ def __init__( def _define(self): mcg_up_diag_circuit, _ = self._dec_mcg_up_diag() gate = mcg_up_diag_circuit.to_instruction() - q = QuantumRegister(self.num_qubits) - mcg_up_diag_circuit = QuantumCircuit(q) + q = QuantumRegister(self.num_qubits, "q") + mcg_up_diag_circuit = QuantumCircuit(q, name="mcg_up_to_diagonal") mcg_up_diag_circuit.append(gate, q[:]) self.definition = mcg_up_diag_circuit @@ -108,8 +108,8 @@ def _dec_mcg_up_diag(self): q=[q_target,q_controls,q_ancilla_zero,q_ancilla_dirty] """ diag = np.ones(2 ** (self.num_controls + 1)).tolist() - q = QuantumRegister(self.num_qubits) - circuit = QuantumCircuit(q) + q = QuantumRegister(self.num_qubits, "q") + circuit = QuantumCircuit(q, name="mcg_up_to_diagonal") (q_target, q_controls, q_ancillas_zero, q_ancillas_dirty) = self._define_qubit_role(q) # ToDo: Keep this threshold updated such that the lowest gate count is achieved: # ToDo: we implement the MCG with a UCGate up to diagonal if the number of controls is diff --git a/qiskit/circuit/library/generalized_gates/uc.py b/qiskit/circuit/library/generalized_gates/uc.py index f54567123e02..c81494da3eec 100644 --- a/qiskit/circuit/library/generalized_gates/uc.py +++ b/qiskit/circuit/library/generalized_gates/uc.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020, 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 @@ -148,10 +148,10 @@ def _dec_ucg(self): the diagonal gate is also returned. """ diag = np.ones(2**self.num_qubits).tolist() - q = QuantumRegister(self.num_qubits) + q = QuantumRegister(self.num_qubits, "q") q_controls = q[1:] q_target = q[0] - circuit = QuantumCircuit(q) + circuit = QuantumCircuit(q, name="uc") # If there is no control, we use the ZYZ decomposition if not q_controls: circuit.unitary(self.params[0], [q]) diff --git a/qiskit/circuit/library/generalized_gates/uc_pauli_rot.py b/qiskit/circuit/library/generalized_gates/uc_pauli_rot.py index 5b5633ec423f..6b637f7d2b2a 100644 --- a/qiskit/circuit/library/generalized_gates/uc_pauli_rot.py +++ b/qiskit/circuit/library/generalized_gates/uc_pauli_rot.py @@ -69,7 +69,7 @@ def __init__(self, angle_list: list[float], rot_axis: str) -> None: def _define(self): ucr_circuit = self._dec_ucrot() gate = ucr_circuit.to_instruction() - q = QuantumRegister(self.num_qubits) + q = QuantumRegister(self.num_qubits, "q") ucr_circuit = QuantumCircuit(q) ucr_circuit.append(gate, q[:]) self.definition = ucr_circuit @@ -79,7 +79,7 @@ def _dec_ucrot(self): Finds a decomposition of a UC rotation gate into elementary gates (C-NOTs and single-qubit rotations). """ - q = QuantumRegister(self.num_qubits) + q = QuantumRegister(self.num_qubits, "q") circuit = QuantumCircuit(q) q_target = q[0] q_controls = q[1:] diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py index 1fd36e52e0c0..6a6623ffce5d 100644 --- a/qiskit/circuit/library/generalized_gates/unitary.py +++ b/qiskit/circuit/library/generalized_gates/unitary.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2019. +# (C) Copyright IBM 2017, 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 @@ -30,14 +30,8 @@ from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.quantum_info.operators.predicates import is_unitary_matrix -# pylint: disable=cyclic-import -from qiskit.synthesis.one_qubit.one_qubit_decompose import OneQubitEulerDecomposer -from qiskit.synthesis.two_qubit.two_qubit_decompose import two_qubit_cnot_decompose - from .isometry import Isometry -_DECOMPOSER1Q = OneQubitEulerDecomposer("U") - if typing.TYPE_CHECKING: from qiskit.quantum_info.operators.base_operator import BaseOperator @@ -143,13 +137,21 @@ def transpose(self): def _define(self): """Calculate a subcircuit that implements this unitary.""" if self.num_qubits == 1: + from qiskit.synthesis.one_qubit.one_qubit_decompose import OneQubitEulerDecomposer + q = QuantumRegister(1, "q") qc = QuantumCircuit(q, name=self.name) - theta, phi, lam, global_phase = _DECOMPOSER1Q.angles_and_phase(self.to_matrix()) + theta, phi, lam, global_phase = OneQubitEulerDecomposer("U").angles_and_phase( + self.to_matrix() + ) qc._append(UGate(theta, phi, lam), [q[0]], []) qc.global_phase = global_phase self.definition = qc elif self.num_qubits == 2: + from qiskit.synthesis.two_qubit.two_qubit_decompose import ( # pylint: disable=cyclic-import + two_qubit_cnot_decompose, + ) + self.definition = two_qubit_cnot_decompose(self.to_matrix()) else: from qiskit.synthesis.unitary.qsd import ( # pylint: disable=cyclic-import diff --git a/qiskit/circuit/library/n_local/evolved_operator_ansatz.py b/qiskit/circuit/library/n_local/evolved_operator_ansatz.py index a50b48ce488e..4bc6bcc58a13 100644 --- a/qiskit/circuit/library/n_local/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/n_local/evolved_operator_ansatz.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -22,7 +22,6 @@ from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info import Operator, Pauli, SparsePauliOp -from qiskit.synthesis.evolution import LieTrotter from .n_local import NLocal @@ -185,6 +184,8 @@ def _evolve_operator(self, operator, time): gate = HamiltonianGate(operator, time) # otherwise, use the PauliEvolutionGate else: + from qiskit.synthesis.evolution import LieTrotter + evolution = LieTrotter() if self._evolution is None else self._evolution gate = PauliEvolutionGate(operator, time, synthesis=evolution) diff --git a/qiskit/circuit/library/n_local/pauli_two_design.py b/qiskit/circuit/library/n_local/pauli_two_design.py index b79f08889387..71b090d08848 100644 --- a/qiskit/circuit/library/n_local/pauli_two_design.py +++ b/qiskit/circuit/library/n_local/pauli_two_design.py @@ -118,7 +118,7 @@ def _build_rotation_layer(self, circuit, param_iter, i): qubits = range(self.num_qubits) # if no gates for this layer were generated, generate them - if i not in self._gates.keys(): + if i not in self._gates: self._gates[i] = list(self._rng.choice(["rx", "ry", "rz"], self.num_qubits)) # if not enough gates exist, add more elif len(self._gates[i]) < self.num_qubits: diff --git a/qiskit/circuit/library/overlap.py b/qiskit/circuit/library/overlap.py index 38f5fb9184e1..2db6a80eedcc 100644 --- a/qiskit/circuit/library/overlap.py +++ b/qiskit/circuit/library/overlap.py @@ -59,7 +59,12 @@ class UnitaryOverlap(QuantumCircuit): """ def __init__( - self, unitary1: QuantumCircuit, unitary2: QuantumCircuit, prefix1="p1", prefix2="p2" + self, + unitary1: QuantumCircuit, + unitary2: QuantumCircuit, + prefix1: str = "p1", + prefix2: str = "p2", + insert_barrier: bool = False, ): """ Args: @@ -69,6 +74,7 @@ def __init__( if it is parameterized. Defaults to ``"p1"``. prefix2: The name of the parameter vector associated to ``unitary2``, if it is parameterized. Defaults to ``"p2"``. + insert_barrier: Whether to insert a barrier between the two unitaries. Raises: CircuitError: Number of qubits in ``unitary1`` and ``unitary2`` does not match. @@ -95,6 +101,8 @@ def __init__( # Generate the actual overlap circuit super().__init__(unitaries[0].num_qubits, name="UnitaryOverlap") self.compose(unitaries[0], inplace=True) + if insert_barrier: + self.barrier() self.compose(unitaries[1].inverse(), inplace=True) diff --git a/qiskit/circuit/library/pauli_evolution.py b/qiskit/circuit/library/pauli_evolution.py index c6d69789bae6..b0af3fbe4163 100644 --- a/qiskit/circuit/library/pauli_evolution.py +++ b/qiskit/circuit/library/pauli_evolution.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021, 2023. +# (C) Copyright IBM 2021, 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 @@ -14,14 +14,16 @@ from __future__ import annotations -from typing import Union, Optional +from typing import Union, Optional, TYPE_CHECKING import numpy as np from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.synthesis.evolution import EvolutionSynthesis, LieTrotter from qiskit.quantum_info import Pauli, SparsePauliOp +if TYPE_CHECKING: + from qiskit.synthesis.evolution import EvolutionSynthesis + class PauliEvolutionGate(Gate): r"""Time-evolution of an operator consisting of Paulis. @@ -107,6 +109,8 @@ class docstring for an example. operator = _to_sparse_pauli_op(operator) if synthesis is None: + from qiskit.synthesis.evolution import LieTrotter + synthesis = LieTrotter() if label is None: diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index a7b62175f207..a82316ed7b09 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -71,18 +71,20 @@ class XXPlusYYGate(Gate): q_1: ┤0 ├ └───────────────┘ - .. math:: - - \newcommand{\rotationangle}{\frac{\theta}{2}} - - R_{XX+YY}(\theta, \beta)\ q_0, q_1 = - RZ_1(-\beta) \cdot \exp\left(-i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_1(\beta) = - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & \cos\left(\rotationangle\right) & -i\sin\left(\rotationangle\right)e^{i\beta} & 0 \\ - 0 & -i\sin\left(\rotationangle\right)e^{-i\beta} & \cos\left(\rotationangle\right) & 0 \\ - 0 & 0 & 0 & 1 - \end{pmatrix} + .. math:: + + \newcommand{\rotationangle}{\frac{\theta}{2}} + + R_{XX+YY}(\theta, \beta)\ q_0, q_1 = + RZ_1(-\beta) \cdot \exp\left(-i \frac{\theta}{2} \frac{XX+YY}{2}\right) \cdot RZ_1(\beta) = + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos\left(\rotationangle\right) & + -i\sin\left(\rotationangle\right)e^{i\beta} & 0 \\ + 0 & -i\sin\left(\rotationangle\right)e^{-i\beta} & + \cos\left(\rotationangle\right) & 0 \\ + 0 & 0 & 0 & 1 + \end{pmatrix} """ def __init__( diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 4d0f73cf0772..abe4e61adf63 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -59,7 +59,7 @@ class Parameter(ParameterExpression): bc.draw('mpl') """ - __slots__ = ("_name", "_uuid", "_hash") + __slots__ = ("_uuid", "_hash") # This `__init__` does not call the super init, because we can't construct the # `_parameter_symbols` dictionary we need to pass to it before we're entirely initialised @@ -79,7 +79,6 @@ def __init__( field when creating two parameters to the same thing (along with the same name) allows them to be equal. This is useful during serialization and deserialization. """ - self._name = name self._uuid = uuid4() if uuid is None else uuid symbol = symengine.Symbol(name) @@ -117,7 +116,7 @@ def subs(self, parameter_map: dict, allow_unknown_parameters: bool = False): @property def name(self): """Returns the name of the :class:`Parameter`.""" - return self._name + return self._symbol_expr.name @property def uuid(self) -> UUID: @@ -143,7 +142,7 @@ def __repr__(self): def __eq__(self, other): if isinstance(other, Parameter): - return (self._uuid, self._name) == (other._uuid, other._name) + return (self._uuid, self._symbol_expr) == (other._uuid, other._symbol_expr) elif isinstance(other, ParameterExpression): return super().__eq__(other) else: @@ -155,7 +154,7 @@ def _hash_key(self): # expression, so its full hash key is split into `(parameter_keys, symbolic_expression)`. # This method lets containing expressions get only the bits they need for equality checks in # the first value, without wasting time re-hashing individual Sympy/Symengine symbols. - return (self._name, self._uuid) + return (self._symbol_expr, self._uuid) def __hash__(self): # This is precached for performance, since it's used a lot and we are immutable. @@ -165,10 +164,10 @@ def __hash__(self): # operation attempts to put this parameter into a hashmap. def __getstate__(self): - return (self._name, self._uuid, self._symbol_expr) + return (self.name, self._uuid, self._symbol_expr) def __setstate__(self, state): - self._name, self._uuid, self._symbol_expr = state + _, self._uuid, self._symbol_expr = state self._parameter_keys = frozenset((self._hash_key(),)) self._hash = hash((self._parameter_keys, self._symbol_expr)) self._parameter_symbols = {self: self._symbol_expr} diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index abd48c686b15..a157f04375a3 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -106,113 +106,878 @@ BitType = TypeVar("BitType", Qubit, Clbit) +# NOTE: +# +# If you're adding methods or attributes to `QuantumCircuit`, be sure to update the class docstring +# to document them in a suitable place. The class is huge, so we do its documentation manually so +# it has at least some amount of organisational structure. + + class QuantumCircuit: - """Create a new circuit. + """Core Qiskit representation of a quantum circuit. + + .. note:: + For more details setting the :class:`QuantumCircuit` in context of all of the data + structures that go with it, how it fits into the rest of the :mod:`qiskit` package, and the + different regimes of quantum-circuit descriptions in Qiskit, see the module-level + documentation of :mod:`qiskit.circuit`. + + Circuit attributes + ================== + + :class:`QuantumCircuit` has a small number of public attributes, which are mostly older + functionality. Most of its functionality is accessed through methods. + + A small handful of the attributes are intentionally mutable, the rest are data attributes that + should be considered immutable. + + ========================= ====================================================================== + Mutable attribute Summary + ========================= ====================================================================== + :attr:`global_phase` The global phase of the circuit, measured in radians. + :attr:`metadata` Arbitrary user mapping, which Qiskit will preserve through the + transpiler, but otherwise completely ignore. + :attr:`name` An optional string name for the circuit. + ========================= ====================================================================== + + ========================= ====================================================================== + Immutable data attribute Summary + ========================= ====================================================================== + :attr:`ancillas` List of :class:`AncillaQubit`\\ s tracked by the circuit. + :attr:`calibrations` Custom user-supplied pulse calibrations for individual instructions. + :attr:`cregs` List of :class:`ClassicalRegister`\\ s tracked by the circuit. + + :attr:`clbits` List of :class:`Clbit`\\ s tracked by the circuit. + :attr:`data` List of individual :class:`CircuitInstruction`\\ s that make up the + circuit. + :attr:`duration` Total duration of the circuit, added by scheduling transpiler passes. + + :attr:`layout` Hardware layout and routing information added by the transpiler. + :attr:`num_ancillas` The number of ancilla qubits in the circuit. + :attr:`num_clbits` The number of clbits in the circuit. + :attr:`num_captured_vars` Number of captured real-time classical variables. + + :attr:`num_declared_vars` Number of locally declared real-time classical variables in the outer + circuit scope. + :attr:`num_input_vars` Number of input real-time classical variables. + :attr:`num_parameters` Number of compile-time :class:`Parameter`\\ s in the circuit. + :attr:`num_qubits` Number of qubits in the circuit. + + :attr:`num_vars` Total number of real-time classical variables in the outer circuit + scope. + :attr:`op_start_times` Start times of scheduled operations, added by scheduling transpiler + passes. + :attr:`parameters` Ordered set-like view of the compile-time :class:`Parameter`\\ s + tracked by the circuit. + :attr:`qregs` List of :class:`QuantumRegister`\\ s tracked by the circuit. + + :attr:`qubits` List of :class:`Qubit`\\ s tracked by the circuit. + :attr:`unit` The unit of the :attr:`duration` field. + ========================= ====================================================================== + + The core attribute is :attr:`data`. This is a sequence-like object that exposes the + :class:`CircuitInstruction`\\ s contained in an ordered form. You generally should not mutate + this object directly; :class:`QuantumCircuit` is only designed for append-only operations (which + should use :meth:`append`). Most operations that mutate circuits in place should be written as + transpiler passes (:mod:`qiskit.transpiler`). + + .. autoattribute:: data + + Alongside the :attr:`data`, the :attr:`global_phase` of a circuit can have some impact on its + output, if the circuit is used to describe a :class:`.Gate` that may be controlled. This is + measured in radians and is directly settable. + + .. autoattribute:: global_phase + + The :attr:`name` of a circuit becomes the name of the :class:`~.circuit.Instruction` or + :class:`.Gate` resulting from :meth:`to_instruction` and :meth:`to_gate` calls, which can be + handy for visualizations. + + .. autoattribute:: name + + You can attach arbitrary :attr:`metadata` to a circuit. No part of core Qiskit will inspect + this or change its behavior based on metadata, but it will be faithfully passed through the + transpiler, so you can tag your circuits yourself. When serializing a circuit with QPY (see + :mod:`qiskit.qpy`), the metadata will be JSON-serialized and you may need to pass a custom + serializer to handle non-JSON-compatible objects within it (see :func:`.qpy.dump` for more + detail). This field is ignored during export to OpenQASM 2 or 3. + + .. autoattribute:: metadata + + :class:`QuantumCircuit` exposes data attributes tracking its internal quantum and classical bits + and registers. These appear as Python :class:`list`\\ s, but you should treat them as + immutable; changing them will *at best* have no effect, and more likely will simply corrupt + the internal data of the :class:`QuantumCircuit`. + + .. autoattribute:: qregs + .. autoattribute:: cregs + .. autoattribute:: qubits + .. autoattribute:: ancillas + .. autoattribute:: clbits + + The :ref:`compile-time parameters ` present in instructions on + the circuit are available in :attr:`parameters`. This has a canonical order (mostly lexical, + except in the case of :class:`.ParameterVector`), which matches the order that parameters will + be assigned when using the list forms of :meth:`assign_parameters`, but also supports + :class:`set`-like constant-time membership testing. + + .. autoattribute:: parameters + + The storage of any :ref:`manual pulse-level calibrations ` for individual + instructions on the circuit is in :attr:`calibrations`. This presents as a :class:`dict`, but + should not be mutated directly; use the methods discussed in :ref:`circuit-calibrations`. + + .. autoattribute:: calibrations + + If you have transpiled your circuit, so you have a physical circuit, you can inspect the + :attr:`layout` attribute for information stored by the transpiler about how the virtual qubits + of the source circuit map to the hardware qubits of your physical circuit, both at the start and + end of the circuit. + + .. autoattribute:: layout + + If your circuit was also *scheduled* as part of a transpilation, it will expose the individual + timings of each instruction, along with the total :attr:`duration` of the circuit. + + .. autoattribute:: duration + .. autoattribute:: unit + .. autoattribute:: op_start_times + + Finally, :class:`QuantumCircuit` exposes several simple properties as dynamic read-only numeric + attributes. + + .. autoattribute:: num_ancillas + .. autoattribute:: num_clbits + .. autoattribute:: num_captured_vars + .. autoattribute:: num_declared_vars + .. autoattribute:: num_input_vars + .. autoattribute:: num_parameters + .. autoattribute:: num_qubits + .. autoattribute:: num_vars + + Creating new circuits + ===================== + + ========================= ===================================================================== + Method Summary + ========================= ===================================================================== + :meth:`__init__` Default constructor of no-instruction circuits. + :meth:`copy` Make a complete copy of an existing circuit. + :meth:`copy_empty_like` Copy data objects from one circuit into a new one without any + instructions. + :meth:`from_instructions` Infer data objects needed from a list of instructions. + :meth:`from_qasm_file` Legacy interface to :func:`.qasm2.load`. + :meth:`from_qasm_str` Legacy interface to :func:`.qasm2.loads`. + ========================= ===================================================================== + + The default constructor (``QuantumCircuit(...)``) produces a circuit with no initial + instructions. The arguments to the default constructor can be used to seed the circuit with + quantum and classical data storage, and to provide a name, global phase and arbitrary metadata. + All of these fields can be expanded later. + + .. automethod:: __init__ + + If you have an existing circuit, you can produce a copy of it using :meth:`copy`, including all + its instructions. This is useful if you want to keep partial circuits while extending another, + or to have a version you can mutate in-place while leaving the prior one intact. - A circuit is a list of instructions bound to some registers. + .. automethod:: copy - Args: - regs (list(:class:`~.Register`) or list(``int``) or list(list(:class:`~.Bit`))): The - registers to be included in the circuit. + Similarly, if you want a circuit that contains all the same data objects (bits, registers, + variables, etc) but with none of the instructions, you can use :meth:`copy_empty_like`. This is + quite common when you want to build up a new layer of a circuit to then use apply onto the back + with :meth:`compose`, or to do a full rewrite of a circuit's instructions. + + .. automethod:: copy_empty_like + + In some cases, it is most convenient to generate a list of :class:`.CircuitInstruction`\\ s + separately to an entire circuit context, and then to build a circuit from this. The + :meth:`from_instructions` constructor will automatically capture all :class:`.Qubit` and + :class:`.Clbit` instances used in the instructions, and create a new :class:`QuantumCircuit` + object that has the correct resources and all the instructions. + + .. automethod:: from_instructions + + :class:`QuantumCircuit` also still has two constructor methods that are legacy wrappers around + the importers in :mod:`qiskit.qasm2`. These automatically apply :ref:`the legacy compatibility + settings ` of :func:`~.qasm2.load` and :func:`~.qasm2.loads`. + + .. automethod:: from_qasm_file + .. automethod:: from_qasm_str + + Data objects on circuits + ======================== - * If a list of :class:`~.Register` objects, represents the :class:`.QuantumRegister` - and/or :class:`.ClassicalRegister` objects to include in the circuit. + .. _circuit-adding-data-objects: + + Adding data objects + ------------------- - For example: + ============================= ================================================================= + Method Adds this kind of data + ============================= ================================================================= + :meth:`add_bits` :class:`.Qubit`\\ s and :class:`.Clbit`\\ s. + :meth:`add_register` :class:`.QuantumRegister` and :class:`.ClassicalRegister`. + :meth:`add_var` :class:`~.expr.Var` nodes with local scope and initializers. + :meth:`add_input` :class:`~.expr.Var` nodes that are treated as circuit inputs. + :meth:`add_capture` :class:`~.expr.Var` nodes captured from containing scopes. + :meth:`add_uninitialized_var` :class:`~.expr.Var` nodes with local scope and undefined state. + ============================= ================================================================= - * ``QuantumCircuit(QuantumRegister(4))`` - * ``QuantumCircuit(QuantumRegister(4), ClassicalRegister(3))`` - * ``QuantumCircuit(QuantumRegister(4, 'qr0'), QuantumRegister(2, 'qr1'))`` + Typically you add most of the data objects (:class:`.Qubit`, :class:`.Clbit`, + :class:`.ClassicalRegister`, etc) to the circuit as part of using the :meth:`__init__` default + constructor, or :meth:`copy_empty_like`. However, it is also possible to add these afterwards. + Typed classical data, such as standalone :class:`~.expr.Var` nodes (see + :ref:`circuit-repr-real-time-classical`), can be both constructed and added with separate + methods. - * If a list of ``int``, the amount of qubits and/or classical bits to include in - the circuit. It can either be a single int for just the number of quantum bits, - or 2 ints for the number of quantum bits and classical bits, respectively. + New registerless :class:`.Qubit` and :class:`.Clbit` objects are added using :meth:`add_bits`. + These objects must not already be present in the circuit. You can check if a bit exists in the + circuit already using :meth:`find_bit`. + + .. automethod:: add_bits + + Registers are added to the circuit with :meth:`add_register`. In this method, it is not an + error if some of the bits are already present in the circuit. In this case, the register will + be an "alias" over the bits. This is not generally well-supported by hardware backends; it is + probably best to stay away from relying on it. The registers a given bit is in are part of the + return of :meth:`find_bit`. - For example: + .. automethod:: add_register - * ``QuantumCircuit(4) # A QuantumCircuit with 4 qubits`` - * ``QuantumCircuit(4, 3) # A QuantumCircuit with 4 qubits and 3 classical bits`` + :ref:`Real-time, typed classical data ` is represented on the + circuit by :class:`~.expr.Var` nodes with a well-defined :class:`~.types.Type`. It is possible + to instantiate these separately to a circuit (see :meth:`.Var.new`), but it is often more + convenient to use circuit methods that will automatically manage the types and expression + initialization for you. The two most common methods are :meth:`add_var` (locally scoped + variables) and :meth:`add_input` (inputs to the circuit). - * If a list of python lists containing :class:`.Bit` objects, a collection of - :class:`.Bit` s to be added to the circuit. + .. automethod:: add_var + .. automethod:: add_input + In addition, there are two lower-level methods that can be useful for programmatic generation of + circuits. When working interactively, you will most likely not need these; most uses of + :meth:`add_uninitialized_var` are part of :meth:`copy_empty_like`, and most uses of + :meth:`add_capture` would be better off using :ref:`the control-flow builder interface + `. - name (str): the name of the quantum circuit. If not set, an - automatically generated string will be assigned. - global_phase (float or ParameterExpression): The global phase of the circuit in radians. - metadata (dict): Arbitrary key value metadata to associate with the - circuit. This gets stored as free-form data in a dict in the - :attr:`~qiskit.circuit.QuantumCircuit.metadata` attribute. It will - not be directly used in the circuit. - inputs: any variables to declare as ``input`` real-time variables for this circuit. These - should already be existing :class:`.expr.Var` nodes that you build from somewhere else; - if you need to create the inputs as well, use :meth:`QuantumCircuit.add_input`. The - variables given in this argument will be passed directly to :meth:`add_input`. A - circuit cannot have both ``inputs`` and ``captures``. - captures: any variables that that this circuit scope should capture from a containing scope. - The variables given here will be passed directly to :meth:`add_capture`. A circuit - cannot have both ``inputs`` and ``captures``. - declarations: any variables that this circuit should declare and initialize immediately. - You can order this input so that later declarations depend on earlier ones (including - inputs or captures). If you need to depend on values that will be computed later at - runtime, use :meth:`add_var` at an appropriate point in the circuit execution. + .. automethod:: add_uninitialized_var + .. automethod:: add_capture - This argument is intended for convenient circuit initialization when you already have a - set of created variables. The variables used here will be directly passed to - :meth:`add_var`, which you can use directly if this is the first time you are creating - the variable. + Working with bits and registers + ------------------------------- - Raises: - CircuitError: if the circuit name, if given, is not valid. - CircuitError: if both ``inputs`` and ``captures`` are given. + A :class:`.Bit` instance is, on its own, just a unique handle for circuits to use in their own + contexts. If you have got a :class:`.Bit` instance and a cirucit, just can find the contexts + that the bit exists in using :meth:`find_bit`, such as its integer index in the circuit and any + registers it is contained in. + + .. automethod:: find_bit + + Similarly, you can query a circuit to see if a register has already been added to it by using + :meth:`has_register`. + + .. automethod:: has_register + + Working with compile-time parameters + ------------------------------------ + + .. seealso:: + :ref:`circuit-compile-time-parameters` + A more complete discussion of what compile-time parametrization is, and how it fits into + Qiskit's data model. + + Unlike bits, registers, and real-time typed classical data, compile-time symbolic parameters are + not manually added to a circuit. Their presence is inferred by being contained in operations + added to circuits and the global phase. An ordered list of all parameters currently in a + circuit is at :attr:`QuantumCircuit.parameters`. + + The most common operation on :class:`.Parameter` instances is to replace them in symbolic + operations with some numeric value, or another symbolic expression. This is done with + :meth:`assign_parameters`. + + .. automethod:: assign_parameters + + The circuit tracks parameters by :class:`.Parameter` instances themselves, and forbids having + multiple parameters of the same name to avoid some problems when interoperating with OpenQASM or + other external formats. You can use :meth:`has_parameter` and :meth:`get_parameter` to query + the circuit for a parameter with the given string name. + + .. automethod:: has_parameter + .. automethod:: get_parameter + + .. _circuit-real-time-methods: + + Working with real-time typed classical data + ------------------------------------------- + + .. seealso:: + :mod:`qiskit.circuit.classical` + Module-level documentation for how the variable-, expression- and type-systems work, the + objects used to represent them, and the classical operations available. + + :ref:`circuit-repr-real-time-classical` + A discussion of how real-time data fits into the entire :mod:`qiskit.circuit` data model + as a whole. + + :ref:`circuit-adding-data-objects` + The methods for adding new :class:`~.expr.Var` variables to a circuit after + initialization. + + You can retrive a :class:`~.expr.Var` instance attached to a circuit by using its variable name + using :meth:`get_var`, or check if a circuit contains a given variable with :meth:`has_var`. + + .. automethod:: get_var + .. automethod:: has_var + + There are also several iterator methods that you can use to get the full set of variables + tracked by a circuit. At least one of :meth:`iter_input_vars` and :meth:`iter_captured_vars` + will be empty, as inputs and captures are mutually exclusive. All of the iterators have + corresponding dynamic properties on :class:`QuantumCircuit` that contain their length: + :attr:`num_vars`, :attr:`num_input_vars`, :attr:`num_captured_vars` and + :attr:`num_declared_vars`. + + .. automethod:: iter_vars + .. automethod:: iter_input_vars + .. automethod:: iter_captured_vars + .. automethod:: iter_declared_vars + + + .. _circuit-adding-operations: + + Adding operations to circuits + ============================= + + You can add anything that implements the :class:`.Operation` interface to a circuit as a single + instruction, though most things you will want to add will be :class:`~.circuit.Instruction` or + :class:`~.circuit.Gate` instances. + + .. seealso:: + :ref:`circuit-operations-instructions` + The :mod:`qiskit.circuit`-level documentation on the different interfaces that Qiskit + uses to define circuit-level instructions. + + .. _circuit-append-compose: + + Methods to add general operations + --------------------------------- + + These are the base methods that handle adding any object, including user-defined ones, onto + circuits. + + =============== =============================================================================== + Method When to use it + =============== =============================================================================== + :meth:`append` Add an instruction as a single object onto a circuit. + :meth:`_append` Same as :meth:`append`, but a low-level interface that elides almost all error + checking. + :meth:`compose` Inline the instructions from one circuit onto another. + :meth:`tensor` Like :meth:`compose`, but strictly for joining circuits that act on disjoint + qubits. + =============== =============================================================================== + + :class:`QuantumCircuit` has two main ways that you will add more operations onto a circuit. + Which to use depends on whether you want to add your object as a single "instruction" + (:meth:`append`), or whether you want to join the instructions from two circuits together + (:meth:`compose`). + + A single instruction or operation appears as a single entry in the :attr:`data` of the circuit, + and as a single box when drawn in the circuit visualizers (see :meth:`draw`). A single + instruction is the "unit" that a hardware backend might be defined in terms of (see + :class:`.Target`). An :class:`~.circuit.Instruction` can come with a + :attr:`~.circuit.Instruction.definition`, which is one rule the transpiler (see + :mod:`qiskit.transpiler`) will be able to fall back on to decompose it for hardware, if needed. + An :class:`.Operation` that is not also an :class:`~.circuit.Instruction` can + only be decomposed if it has some associated high-level synthesis method registered for it (see + :mod:`qiskit.transpiler.passes.synthesis.plugin`). + + A :class:`QuantumCircuit` alone is not a single :class:`~.circuit.Instruction`; it is rather + more complicated, since it can, in general, represent a complete program with typed classical + memory inputs and outputs, and control flow. Qiskit's (and most hardware's) data model does not + yet have the concept of re-usable callable subroutines with virtual quantum operands. You can + convert simple circuits that act only on qubits with unitary operations into a :class:`.Gate` + using :meth:`to_gate`, and simple circuits acting only on qubits and clbits into a + :class:`~.circuit.Instruction` with :meth:`to_instruction`. + + When you have an :class:`.Operation`, :class:`~.circuit.Instruction`, or :class:`.Gate`, add it + to the circuit, specifying the qubit and clbit arguments with :meth:`append`. + + .. automethod:: append + + :meth:`append` does quite substantial error checking to ensure that you cannot accidentally + break the data model of :class:`QuantumCircuit`. If you are programmatically generating a + circuit from known-good data, you can elide much of this error checking by using the fast-path + appender :meth:`_append`, but at the risk that the caller is responsible for ensuring they are + passing only valid data. + + .. automethod:: _append + + In other cases, you may want to join two circuits together, applying the instructions from one + circuit onto specified qubits and clbits on another circuit. This "inlining" operation is + called :meth:`compose` in Qiskit. :meth:`compose` is, in general, more powerful than + a :meth:`to_instruction`-plus-:meth:`append` combination for joining two circuits, because it + can also link typed classical data together, and allows for circuit control-flow operations to + be joined onto another circuit. + + The downsides to :meth:`compose` are that it is a more complex operation that can involve more + rewriting of the operand, and that it necessarily must move data from one circuit object to + another. If you are building up a circuit for yourself and raw performance is a core goal, + consider passing around your base circuit and having different parts of your algorithm write + directly to the base circuit, rather than building a temporary layer circuit. + + .. automethod:: compose + + If you are trying to join two circuits that will apply to completely disjoint qubits and clbits, + :meth:`tensor` is a convenient wrapper around manually adding bit objects and calling + :meth:`compose`. + + .. automethod:: tensor + + As some rules of thumb: + + * If you have a single :class:`.Operation`, :class:`~.circuit.Instruction` or :class:`.Gate`, + you should definitely use :meth:`append` or :meth:`_append`. + * If you have a :class:`QuantumCircuit` that represents a single atomic instruction for a larger + circuit that you want to re-use, you probably want to call :meth:`to_instruction` or + :meth:`to_gate`, and then apply the result of that to the circuit using :meth:`append`. + * If you have a :class:`QuantumCircuit` that represents a larger "layer" of another circuit, or + contains typed classical variables or control flow, you should use :meth:`compose` to merge it + onto another circuit. + * :meth:`tensor` is wanted far more rarely than either :meth:`append` or :meth:`compose`. + Internally, it is mostly a wrapper around :meth:`add_bits` and :meth:`compose`. + + Some potential pitfalls to beware of: + + * Even if you re-use a custom :class:`~.circuit.Instruction` during circuit construction, the + transpiler will generally have to "unroll" each invocation of it to its inner decomposition + before beginning work on it. This should not prevent you from using the + :meth:`to_instruction`-plus-:meth:`append` pattern, as the transpiler will improve in this + regard over time. + * :meth:`compose` will, by default, produce a new circuit for backwards compatibility. This is + more expensive, and not usually what you want, so you should set ``inplace=True``. + * Both :meth:`append` and :meth:`compose` (but not :meth:`_append`) have a ``copy`` keyword + argument that defaults to ``True``. In these cases, the incoming :class:`.Operation` + instances will be copied if Qiskit detects that the objects have mutability about them (such + as taking gate parameters). If you are sure that you will not re-use the objects again in + other places, you should set ``copy=False`` to prevent this copying, which can be a + substantial speed-up for large objects. + + Methods to add standard instructions + ------------------------------------ + + The :class:`QuantumCircuit` class has helper methods to add many of the Qiskit standard-library + instructions and gates onto a circuit. These are generally equivalent to manually constructing + an instance of the relevent :mod:`qiskit.circuit.library` object, then passing that to + :meth:`append` with the remaining arguments placed into the ``qargs`` and ``cargs`` fields as + appropriate. + + The following methods apply special non-unitary :class:`~.circuit.Instruction` operations to the + circuit: + + =============================== ==================================================== + :class:`QuantumCircuit` method :mod:`qiskit.circuit` :class:`~.circuit.Instruction` + =============================== ==================================================== + :meth:`barrier` :class:`Barrier` + :meth:`delay` :class:`Delay` + :meth:`initialize` :class:`~library.Initialize` + :meth:`measure` :class:`Measure` + :meth:`reset` :class:`Reset` + :meth:`store` :class:`Store` + =============================== ==================================================== + + These methods apply uncontrolled unitary :class:`.Gate` instances to the circuit: + + =============================== ============================================ + :class:`QuantumCircuit` method :mod:`qiskit.circuit.library` :class:`.Gate` + =============================== ============================================ + :meth:`dcx` :class:`~library.DCXGate` + :meth:`ecr` :class:`~library.ECRGate` + :meth:`h` :class:`~library.HGate` + :meth:`id` :class:`~library.IGate` + :meth:`iswap` :class:`~library.iSwapGate` + :meth:`ms` :class:`~library.MSGate` + :meth:`p` :class:`~library.PhaseGate` + :meth:`pauli` :class:`~library.PauliGate` + :meth:`prepare_state` :class:`~library.StatePreparation` + :meth:`r` :class:`~library.RGate` + :meth:`rcccx` :class:`~library.RC3XGate` + :meth:`rccx` :class:`~library.RCCXGate` + :meth:`rv` :class:`~library.RVGate` + :meth:`rx` :class:`~library.RXGate` + :meth:`rxx` :class:`~library.RXXGate` + :meth:`ry` :class:`~library.RYGate` + :meth:`ryy` :class:`~library.RYYGate` + :meth:`rz` :class:`~library.RZGate` + :meth:`rzx` :class:`~library.RZXGate` + :meth:`rzz` :class:`~library.RZZGate` + :meth:`s` :class:`~library.SGate` + :meth:`sdg` :class:`~library.SdgGate` + :meth:`swap` :class:`~library.SwapGate` + :meth:`sx` :class:`~library.SXGate` + :meth:`sxdg` :class:`~library.SXdgGate` + :meth:`t` :class:`~library.TGate` + :meth:`tdg` :class:`~library.TdgGate` + :meth:`u` :class:`~library.UGate` + :meth:`unitary` :class:`~library.UnitaryGate` + :meth:`x` :class:`~library.XGate` + :meth:`y` :class:`~library.YGate` + :meth:`z` :class:`~library.ZGate` + =============================== ============================================ + + The following methods apply :class:`Gate` instances that are also controlled gates, so are + direct subclasses of :class:`ControlledGate`: + + =============================== ====================================================== + :class:`QuantumCircuit` method :mod:`qiskit.circuit.library` :class:`.ControlledGate` + =============================== ====================================================== + :meth:`ccx` :class:`~library.CCXGate` + :meth:`ccz` :class:`~library.CCZGate` + :meth:`ch` :class:`~library.CHGate` + :meth:`cp` :class:`~library.CPhaseGate` + :meth:`crx` :class:`~library.CRXGate` + :meth:`cry` :class:`~library.CRYGate` + :meth:`crz` :class:`~library.CRZGate` + :meth:`cs` :class:`~library.CSGate` + :meth:`csdg` :class:`~library.CSdgGate` + :meth:`cswap` :class:`~library.CSwapGate` + :meth:`csx` :class:`~library.CSXGate` + :meth:`cu` :class:`~library.CUGate` + :meth:`cx` :class:`~library.CXGate` + :meth:`cy` :class:`~library.CYGate` + :meth:`cz` :class:`~library.CZGate` + =============================== ====================================================== + + Finally, these methods apply particular generalized multiply controlled gates to the circuit, + often with eager syntheses. They are listed in terms of the *base* gate they are controlling, + since their exact output is often a synthesised version of a gate. + + =============================== ================================================= + :class:`QuantumCircuit` method Base :mod:`qiskit.circuit.library` :class:`.Gate` + =============================== ================================================= + :meth:`mcp` :class:`~library.PhaseGate` + :meth:`mcrx` :class:`~library.RXGate` + :meth:`mcry` :class:`~library.RYGate` + :meth:`mcrz` :class:`~library.RZGate` + :meth:`mcx` :class:`~library.XGate` + =============================== ================================================= + + The rest of this section is the API listing of all the individual methods; the tables above are + summaries whose links will jump you to the correct place. + + .. automethod:: barrier + .. automethod:: ccx + .. automethod:: ccz + .. automethod:: ch + .. automethod:: cp + .. automethod:: crx + .. automethod:: cry + .. automethod:: crz + .. automethod:: cs + .. automethod:: csdg + .. automethod:: cswap + .. automethod:: csx + .. automethod:: cu + .. automethod:: cx + .. automethod:: cy + .. automethod:: cz + .. automethod:: dcx + .. automethod:: delay + .. automethod:: ecr + .. automethod:: h + .. automethod:: id + .. automethod:: initialize + .. automethod:: iswap + .. automethod:: mcp + .. automethod:: mcrx + .. automethod:: mcry + .. automethod:: mcrz + .. automethod:: mcx + .. automethod:: measure + .. automethod:: ms + .. automethod:: p + .. automethod:: pauli + .. automethod:: prepare_state + .. automethod:: r + .. automethod:: rcccx + .. automethod:: rccx + .. automethod:: reset + .. automethod:: rv + .. automethod:: rx + .. automethod:: rxx + .. automethod:: ry + .. automethod:: ryy + .. automethod:: rz + .. automethod:: rzx + .. automethod:: rzz + .. automethod:: s + .. automethod:: sdg + .. automethod:: store + .. automethod:: swap + .. automethod:: sx + .. automethod:: sxdg + .. automethod:: t + .. automethod:: tdg + .. automethod:: u + .. automethod:: unitary + .. automethod:: x + .. automethod:: y + .. automethod:: z + + + .. _circuit-control-flow-methods: + + Adding control flow to circuits + ------------------------------- + + .. seealso:: + :ref:`circuit-control-flow-repr` + + Discussion of how control-flow operations are represented in the whole :mod:`qiskit.circuit` + context. + + ============================== ================================================================ + :class:`QuantumCircuit` method Control-flow instruction + ============================== ================================================================ + :meth:`if_test` :class:`.IfElseOp` with only a ``True`` body. + :meth:`if_else` :class:`.IfElseOp` with both ``True`` and ``False`` bodies. + :meth:`while_loop` :class:`.WhileLoopOp`. + :meth:`switch` :class:`.SwitchCaseOp`. + :meth:`for_loop` :class:`.ForLoopOp`. + :meth:`break_loop` :class:`.BreakLoopOp`. + :meth:`continue_loop` :class:`.ContinueLoopOp`. + ============================== ================================================================ + + :class:`QuantumCircuit` has corresponding methods for all of the control-flow operations that + are supported by Qiskit. These have two forms for calling them. The first is a very + straightfowards convenience wrapper that takes in the block bodies of the instructions as + :class:`QuantumCircuit` arguments, and simply constructs and appends the corresponding + :class:`.ControlFlowOp`. + + The second form, which we strongly recommend you use for constructing control flow, is called + *the builder interface*. Here, the methods take only the real-time discriminant of the + operation, and return `context managers + `__ that you enter using + ``with``. You can then use regular :class:`QuantumCircuit` methods within those blocks to build + up the control-flow bodies, and Qiskit will automatically track which of the data resources are + needed for the inner blocks, building the complete :class:`.ControlFlowOp` as you leave the + ``with`` statement. It is far simpler and less error-prone to build control flow + programmatically this way. + + .. + TODO: expand the examples of the builder interface. + + .. automethod:: break_loop + .. automethod:: continue_loop + .. automethod:: for_loop + .. automethod:: if_else + .. automethod:: if_test + .. automethod:: switch + .. automethod:: while_loop + + + Converting circuits to single objects + ------------------------------------- + + As discussed in :ref:`circuit-append-compose`, you can convert a circuit to either an + :class:`~.circuit.Instruction` or a :class:`.Gate` using two helper methods. + + .. automethod:: to_instruction + .. automethod:: to_gate + + + Helper mutation methods + ----------------------- + + There are two higher-level methods on :class:`QuantumCircuit` for appending measurements to the + end of a circuit. Note that by default, these also add an extra register. + + .. automethod:: measure_active + .. automethod:: measure_all + + There are two "subtractive" methods on :class:`QuantumCircuit` as well. This is not a use-case + that :class:`QuantumCircuit` is designed for; typically you should just look to use + :meth:`copy_empty_like` in place of :meth:`clear`, and run :meth:`remove_final_measurements` as + its transpiler-pass form :class:`.RemoveFinalMeasurements`. + + .. automethod:: clear + .. automethod:: remove_final_measurements + + .. _circuit-calibrations: + + Manual calibration of instructions + ---------------------------------- + + :class:`QuantumCircuit` can store :attr:`calibrations` of instructions that define the pulses + used to run them on one particular hardware backend. You can + + .. automethod:: add_calibration + .. automethod:: has_calibration_for + + + Circuit properties + ================== + + Simple circuit metrics + ---------------------- + + When constructing quantum circuits, there are several properties that help quantify + the "size" of the circuits, and their ability to be run on a noisy quantum device. + Some of these, like number of qubits, are straightforward to understand, while others + like depth and number of tensor components require a bit more explanation. Here we will + explain all of these properties, and, in preparation for understanding how circuits change + when run on actual devices, highlight the conditions under which they change. + + Consider the following circuit: + + .. plot:: + :include-source: + + from qiskit import QuantumCircuit + qc = QuantumCircuit(12) + for idx in range(5): + qc.h(idx) + qc.cx(idx, idx+5) + + qc.cx(1, 7) + qc.x(8) + qc.cx(1, 9) + qc.x(7) + qc.cx(1, 11) + qc.swap(6, 11) + qc.swap(6, 9) + qc.swap(6, 10) + qc.x(6) + qc.draw('mpl') + + From the plot, it is easy to see that this circuit has 12 qubits, and a collection of + Hadamard, CNOT, X, and SWAP gates. But how to quantify this programmatically? Because we + can do single-qubit gates on all the qubits simultaneously, the number of qubits in this + circuit is equal to the :meth:`width` of the circuit:: + + assert qc.width() == 12 + + We can also just get the number of qubits directly using :attr:`num_qubits`:: + + assert qc.num_qubits == 12 + + .. important:: + + For a quantum circuit composed from just qubits, the circuit width is equal + to the number of qubits. This is the definition used in quantum computing. However, + for more complicated circuits with classical registers, and classically controlled gates, + this equivalence breaks down. As such, from now on we will not refer to the number of + qubits in a quantum circuit as the width. + + It is also straightforward to get the number and type of the gates in a circuit using + :meth:`count_ops`:: + + qc.count_ops() - Examples: + .. parsed-literal:: - Construct a simple Bell state circuit. + OrderedDict([('cx', 8), ('h', 5), ('x', 3), ('swap', 3)]) - .. plot:: - :include-source: + We can also get just the raw count of operations by computing the circuits + :meth:`size`:: - from qiskit import QuantumCircuit + assert qc.size() == 19 - qc = QuantumCircuit(2, 2) - qc.h(0) - qc.cx(0, 1) - qc.measure([0, 1], [0, 1]) - qc.draw('mpl') + A particularly important circuit property is known as the circuit :meth:`depth`. The depth + of a quantum circuit is a measure of how many "layers" of quantum gates, executed in + parallel, it takes to complete the computation defined by the circuit. Because quantum + gates take time to implement, the depth of a circuit roughly corresponds to the amount of + time it takes the quantum computer to execute the circuit. Thus, the depth of a circuit + is one important quantity used to measure if a quantum circuit can be run on a device. - Construct a 5-qubit GHZ circuit. + The depth of a quantum circuit has a mathematical definition as the longest path in a + directed acyclic graph (DAG). However, such a definition is a bit hard to grasp, even for + experts. Fortunately, the depth of a circuit can be easily understood by anyone familiar + with playing `Tetris `_. Lets see how to compute this + graphically: - .. code-block:: + .. image:: /source_images/depth.gif - from qiskit import QuantumCircuit + We can verify our graphical result using :meth:`QuantumCircuit.depth`:: - qc = QuantumCircuit(5) - qc.h(0) - qc.cx(0, range(1, 5)) - qc.measure_all() + assert qc.depth() == 9 - Construct a 4-qubit Bernstein-Vazirani circuit using registers. + .. automethod:: count_ops + .. automethod:: depth + .. automethod:: get_instructions + .. automethod:: num_connected_components + .. automethod:: num_nonlocal_gates + .. automethod:: num_tensor_factors + .. automethod:: num_unitary_factors + .. automethod:: size + .. automethod:: width - .. plot:: - :include-source: + Accessing scheduling information + -------------------------------- - from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit + If a :class:`QuantumCircuit` has been scheduled as part of a transpilation pipeline, the timing + information for individual qubits can be accessed. The whole-circuit timing information is + available through the :attr:`duration`, :attr:`unit` and :attr:`op_start_times` attributes. + + .. automethod:: qubit_duration + .. automethod:: qubit_start_time + .. automethod:: qubit_stop_time + + Instruction-like methods + ======================== - qr = QuantumRegister(3, 'q') - anc = QuantumRegister(1, 'ancilla') - cr = ClassicalRegister(3, 'c') - qc = QuantumCircuit(qr, anc, cr) + .. + These methods really shouldn't be on `QuantumCircuit` at all. They're generally more + appropriate as `Instruction` or `Gate` methods. `reverse_ops` shouldn't be a method _full + stop_---it was copying a `DAGCircuit` method from an implementation detail of the original + `SabreLayout` pass in Qiskit. + + :class:`QuantumCircuit` also contains a small number of methods that are very + :class:`~.circuit.Instruction`-like in detail. You may well find better integration and more + API support if you first convert your circuit to an :class:`~.circuit.Instruction` + (:meth:`to_instruction`) or :class:`.Gate` (:meth:`to_gate`) as appropriate, then call the + corresponding method. - qc.x(anc[0]) - qc.h(anc[0]) - qc.h(qr[0:3]) - qc.cx(qr[0:3], anc[0]) - qc.h(qr[0:3]) - qc.barrier(qr) - qc.measure(qr, cr) + .. automethod:: control + .. automethod:: inverse + .. automethod:: power + .. automethod:: repeat + .. automethod:: reverse_ops - qc.draw('mpl') + Visualization + ============= + + Qiskit includes some drawing tools to give you a quick feel for what your circuit looks like. + This tooling is primarily targeted at producing either a `Matplotlib + `__- or text-based drawing. There is also a lesser-featured LaTeX + backend for drawing, but this is only for simple circuits, and is not as actively maintained. + + .. seealso:: + :mod:`qiskit.visualization` + The primary documentation for all of Qiskit's visualization tooling. + + .. automethod:: draw + + In addition to the core :meth:`draw` driver, there are two visualization-related helper methods, + which are mostly useful for quickly unwrapping some inner instructions or reversing the + :ref:`qubit-labelling conventions ` in the drawing. For more general + mutation, including basis-gate rewriting, you should use the transpiler + (:mod:`qiskit.transpiler`). + + .. automethod:: decompose + .. automethod:: reverse_bits + + Internal utilities + ================== + + These functions are not intended for public use, but were accidentally left documented in the + public API during the 1.0 release. They will be removed in Qiskit 2.0, but will be supported + until then. + + .. automethod:: cast + .. automethod:: cbit_argument_conversion + .. automethod:: cls_instances + .. automethod:: cls_prefix + .. automethod:: qbit_argument_conversion """ instances = 0 @@ -228,6 +993,69 @@ def __init__( captures: Iterable[expr.Var] = (), declarations: Mapping[expr.Var, expr.Expr] | Iterable[Tuple[expr.Var, expr.Expr]] = (), ): + """ + Default constructor of :class:`QuantumCircuit`. + + .. + `QuantumCirucit` documents its `__init__` method explicitly, unlike most classes where + it's implicitly appended to the class-level documentation, just because the class is so + huge and has a lot of introductory material to its class docstring. + + Args: + regs: The registers to be included in the circuit. + + * If a list of :class:`~.Register` objects, represents the :class:`.QuantumRegister` + and/or :class:`.ClassicalRegister` objects to include in the circuit. + + For example: + + * ``QuantumCircuit(QuantumRegister(4))`` + * ``QuantumCircuit(QuantumRegister(4), ClassicalRegister(3))`` + * ``QuantumCircuit(QuantumRegister(4, 'qr0'), QuantumRegister(2, 'qr1'))`` + + * If a list of ``int``, the amount of qubits and/or classical bits to include in + the circuit. It can either be a single int for just the number of quantum bits, + or 2 ints for the number of quantum bits and classical bits, respectively. + + For example: + + * ``QuantumCircuit(4) # A QuantumCircuit with 4 qubits`` + * ``QuantumCircuit(4, 3) # A QuantumCircuit with 4 qubits and 3 classical bits`` + + * If a list of python lists containing :class:`.Bit` objects, a collection of + :class:`.Bit` s to be added to the circuit. + + name: the name of the quantum circuit. If not set, an automatically generated string + will be assigned. + global_phase: The global phase of the circuit in radians. + metadata: Arbitrary key value metadata to associate with the circuit. This gets + stored as free-form data in a dict in the + :attr:`~qiskit.circuit.QuantumCircuit.metadata` attribute. It will not be directly + used in the circuit. + inputs: any variables to declare as ``input`` runtime variables for this circuit. These + should already be existing :class:`.expr.Var` nodes that you build from somewhere + else; if you need to create the inputs as well, use + :meth:`QuantumCircuit.add_input`. The variables given in this argument will be + passed directly to :meth:`add_input`. A circuit cannot have both ``inputs`` and + ``captures``. + captures: any variables that that this circuit scope should capture from a containing + scope. The variables given here will be passed directly to :meth:`add_capture`. A + circuit cannot have both ``inputs`` and ``captures``. + declarations: any variables that this circuit should declare and initialize immediately. + You can order this input so that later declarations depend on earlier ones + (including inputs or captures). If you need to depend on values that will be + computed later at runtime, use :meth:`add_var` at an appropriate point in the + circuit execution. + + This argument is intended for convenient circuit initialization when you already + have a set of created variables. The variables used here will be directly passed to + :meth:`add_var`, which you can use directly if this is the first time you are + creating the variable. + + Raises: + CircuitError: if the circuit name, if given, is not valid. + CircuitError: if both ``inputs`` and ``captures`` are given. + """ if any(not isinstance(reg, (list, QuantumRegister, ClassicalRegister)) for reg in regs): # check if inputs are integers, but also allow e.g. 2.0 @@ -244,6 +1072,8 @@ def __init__( regs = tuple(int(reg) for reg in regs) # cast to int self._base_name = None + self.name: str + """A human-readable name for the circuit.""" if name is None: self._base_name = self.cls_prefix() self._name_update() @@ -273,7 +1103,11 @@ def __init__( ] = [] self.qregs: list[QuantumRegister] = [] + """A list of the :class:`QuantumRegister`\\ s in this circuit. You should not mutate + this.""" self.cregs: list[ClassicalRegister] = [] + """A list of the :class:`ClassicalRegister`\\ s in this circuit. You should not mutate + this.""" # Dict mapping Qubit or Clbit instances to tuple comprised of 0) the # corresponding index in circuit.{qubits,clbits} and 1) a list of @@ -314,9 +1148,16 @@ def __init__( for var, initial in declarations: self.add_var(var, initial) - self.duration = None + 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.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.""" @staticmethod def from_instructions( @@ -333,7 +1174,7 @@ def from_instructions( global_phase: ParameterValueType = 0, metadata: dict | None = None, ) -> "QuantumCircuit": - """Construct a circuit from an iterable of CircuitInstructions. + """Construct a circuit from an iterable of :class:`.CircuitInstruction`\\ s. Args: instructions: The instructions to add to the circuit. @@ -390,7 +1231,7 @@ def layout(self) -> Optional[TranspileLayout]: @property def data(self) -> QuantumCircuitData: - """Return the circuit data (instructions and context). + """The circuit data (instructions and context). Returns: QuantumCircuitData: a list-like object containing the :class:`.CircuitInstruction`\\ s @@ -884,9 +1725,13 @@ def compose( var_remap: Mapping[str | expr.Var, str | expr.Var] | None = None, inline_captures: bool = False, ) -> Optional["QuantumCircuit"]: - """Compose circuit with ``other`` circuit or instruction, optionally permuting wires. + """Apply the instructions from one circuit onto specified qubits and/or clbits on another. + + .. note:: - ``other`` can be narrower or of equal width to ``self``. + By default, this creates a new circuit object, leaving ``self`` untouched. For most + uses of this function, it is far more efficient to set ``inplace=True`` and modify the + base circuit in-place. When dealing with realtime variables (:class:`.expr.Var` instances), there are two principal strategies for using :meth:`compose`: @@ -915,8 +1760,6 @@ def compose( front (bool): If True, front composition will be performed. This is not possible within control-flow builder context managers. inplace (bool): If True, modify the object. Otherwise return composed circuit. - wrap (bool): If True, wraps the other circuit into a gate (or instruction, depending on - whether it contains only unitary instructions) before composing it onto self. copy (bool): If ``True`` (the default), then the input is treated as shared, and any contained instructions will be copied, if they might need to be mutated in the future. You can set this to ``False`` if the input should be considered owned by @@ -941,6 +1784,11 @@ def compose( If this is ``False`` (the default), then all variables in ``other`` will be required to be distinct from those in ``self``, and new declarations will be made for them. + wrap (bool): If True, wraps the other circuit into a gate (or instruction, depending on + whether it contains only unitary instructions) before composing it onto self. + Rather than using this option, it is almost always better to manually control this + yourself by using :meth:`to_instruction` or :meth:`to_gate`, and then call + :meth:`append`. Returns: QuantumCircuit: the composed circuit (returns None if inplace==True). @@ -1294,23 +2142,20 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu @property def qubits(self) -> list[Qubit]: - """ - Returns a list of quantum bits in the order that the registers were added. - """ + """A list of :class:`Qubit`\\ s in the order that they were added. You should not mutate + this.""" return self._data.qubits @property def clbits(self) -> list[Clbit]: - """ - Returns a list of classical bits in the order that the registers were added. - """ + """A list of :class:`Clbit`\\ s in the order that they were added. You should not mutate + this.""" return self._data.clbits @property def ancillas(self) -> list[AncillaQubit]: - """ - Returns a list of ancilla bits in the order that the registers were added. - """ + """A list of :class:`AncillaQubit`\\ s in the order that they were added. You should not + mutate this.""" return self._ancillas @property @@ -1557,33 +2402,31 @@ def append( # Preferred new style. @typing.overload - def _append( - self, instruction: CircuitInstruction, _qargs: None = None, _cargs: None = None - ) -> CircuitInstruction: ... + def _append(self, instruction: CircuitInstruction) -> CircuitInstruction: ... # To-be-deprecated old style. @typing.overload def _append( self, - operation: Operation, + instruction: Operation, qargs: Sequence[Qubit], cargs: Sequence[Clbit], ) -> Operation: ... - def _append( - self, - instruction: CircuitInstruction | Instruction, - qargs: Sequence[Qubit] | None = None, - cargs: Sequence[Clbit] | None = None, - ): + def _append(self, instruction, qargs=(), cargs=()): """Append an instruction to the end of the circuit, modifying the circuit in place. .. warning:: This is an internal fast-path function, and it is the responsibility of the caller to ensure that all the arguments are valid; there is no error checking here. In - particular, all the qubits and clbits must already exist in the circuit and there can be - no duplicates in the list. + particular: + + * all the qubits and clbits must already exist in the circuit and there can be no + duplicates in the list. + * any control-flow operations or classically conditioned instructions must act only on + variables present in the circuit. + * the circuit must not be within a control-flow builder context. .. note:: @@ -1596,12 +2439,18 @@ def _append( constructs of the control-flow builder interface. Args: - instruction: Operation instance to append - qargs: Qubits to attach the instruction to. - cargs: Clbits to attach the instruction to. + instruction: A complete well-formed :class:`.CircuitInstruction` of the operation and + its context to be added. + + In the legacy compatibility form, this can be a bare :class:`.Operation`, in which + case ``qargs`` and ``cargs`` must be explicitly given. + qargs: Legacy argument for qubits to attach the bare :class:`.Operation` to. Ignored if + the first argument is in the preferential :class:`.CircuitInstruction` form. + cargs: Legacy argument for clbits to attach the bare :class:`.Operation` to. Ignored if + the first argument is in the preferential :class:`.CircuitInstruction` form. Returns: - Operation: a handle to the instruction that was just added + CircuitInstruction: a handle to the instruction that was just added. :meta public: """ @@ -2114,24 +2963,52 @@ def add_bits(self, bits: Iterable[Bit]) -> None: def find_bit(self, bit: Bit) -> BitLocations: """Find locations in the circuit which can be used to reference a given :obj:`~Bit`. + In particular, this function can find the integer index of a qubit, which corresponds to its + hardware index for a transpiled circuit. + + .. note:: + The circuit index of a :class:`.AncillaQubit` will be its index in :attr:`qubits`, not + :attr:`ancillas`. + Args: bit (Bit): The bit to locate. Returns: namedtuple(int, List[Tuple(Register, int)]): A 2-tuple. The first element (``index``) - contains the index at which the ``Bit`` can be found (in either - :obj:`~QuantumCircuit.qubits`, :obj:`~QuantumCircuit.clbits`, depending on its - type). The second element (``registers``) is a list of ``(register, index)`` - pairs with an entry for each :obj:`~Register` in the circuit which contains the - :obj:`~Bit` (and the index in the :obj:`~Register` at which it can be found). - - Notes: - The circuit index of an :obj:`~AncillaQubit` will be its index in - :obj:`~QuantumCircuit.qubits`, not :obj:`~QuantumCircuit.ancillas`. + contains the index at which the ``Bit`` can be found (in either + :obj:`~QuantumCircuit.qubits`, :obj:`~QuantumCircuit.clbits`, depending on its + type). The second element (``registers``) is a list of ``(register, index)`` + pairs with an entry for each :obj:`~Register` in the circuit which contains the + :obj:`~Bit` (and the index in the :obj:`~Register` at which it can be found). Raises: CircuitError: If the supplied :obj:`~Bit` was of an unknown type. CircuitError: If the supplied :obj:`~Bit` could not be found on the circuit. + + Examples: + Loop through a circuit, getting the qubit and clbit indices of each operation:: + + from qiskit.circuit import QuantumCircuit, Qubit + + qc = QuantumCircuit(3, 3) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + qc.measure([0, 1, 2], [0, 1, 2]) + + # The `.qubits` and `.clbits` fields are not integers. + assert isinstance(qc.data[0].qubits[0], Qubit) + # ... but we can use `find_bit` to retrieve them. + assert qc.find_bit(qc.data[0].qubits[0]).index == 0 + + simple = [ + ( + instruction.operation.name, + [qc.find_bit(bit).index for bit in instruction.qubits], + [qc.find_bit(bit).index for bit in instruction.clbits], + ) + for instruction in qc.data + ] """ try: @@ -2157,18 +3034,22 @@ def to_instruction( parameter_map: dict[Parameter, ParameterValueType] | None = None, label: str | None = None, ) -> Instruction: - """Create an Instruction out of this circuit. + """Create an :class:`~.circuit.Instruction` out of this circuit. + + .. seealso:: + :func:`circuit_to_instruction` + The underlying driver of this method. Args: - parameter_map(dict): For parameterized circuits, a mapping from + parameter_map: For parameterized circuits, a mapping from parameters in the circuit to parameters to be used in the instruction. If None, existing circuit parameters will also parameterize the instruction. - label (str): Optional gate label. + label: Optional gate label. Returns: - qiskit.circuit.Instruction: a composite instruction encapsulating this circuit - (can be decomposed back) + qiskit.circuit.Instruction: a composite instruction encapsulating this circuit (can be + decomposed back). """ from qiskit.converters.circuit_to_instruction import circuit_to_instruction @@ -2179,18 +3060,21 @@ def to_gate( parameter_map: dict[Parameter, ParameterValueType] | None = None, label: str | None = None, ) -> Gate: - """Create a Gate out of this circuit. + """Create a :class:`.Gate` out of this circuit. The circuit must act only qubits and + contain only unitary operations. + + .. seealso:: + :func:`circuit_to_gate` + The underlying driver of this method. Args: - parameter_map(dict): For parameterized circuits, a mapping from - parameters in the circuit to parameters to be used in the - gate. If None, existing circuit parameters will also - parameterize the gate. - label (str): Optional gate label. + parameter_map: For parameterized circuits, a mapping from parameters in the circuit to + parameters to be used in the gate. If ``None``, existing circuit parameters will + also parameterize the gate. + label : Optional gate label. Returns: - Gate: a composite gate encapsulating this circuit - (can be decomposed back) + Gate: a composite gate encapsulating this circuit (can be decomposed back). """ from qiskit.converters.circuit_to_gate import circuit_to_gate @@ -2417,25 +3301,36 @@ def size( def depth( self, - filter_function: Callable[..., int] = lambda x: not getattr( + filter_function: Callable[[CircuitInstruction], bool] = lambda x: not getattr( x.operation, "_directive", False ), ) -> int: """Return circuit depth (i.e., length of critical path). Args: - filter_function (callable): A function to filter instructions. - Should take as input a tuple of (Instruction, list(Qubit), list(Clbit)). - Instructions for which the function returns False are ignored in the - computation of the circuit depth. - By default filters out "directives", such as barrier or snapshot. + filter_function: A function to decide which instructions count to increase depth. + Should take as a single positional input a :class:`CircuitInstruction`. + Instructions for which the function returns ``False`` are ignored in the + computation of the circuit depth. By default filters out "directives", such as + :class:`.Barrier`. Returns: int: Depth of circuit. - Notes: - The circuit depth and the DAG depth need not be the - same. + Examples: + Simple calculation of total circuit depth:: + + from qiskit.circuit import QuantumCircuit + qc = QuantumCircuit(4) + qc.h(0) + qc.cx(0, 1) + qc.h(2) + qc.cx(2, 3) + assert qc.depth() == 2 + + Modifying the previous example to only calculate the depth of multi-qubit gates:: + + assert qc.depth(lambda instr: len(instr.qubits) > 1) == 1 """ # Assign each bit in the circuit a unique integer # to index into op_stack. @@ -2773,6 +3668,11 @@ def clear(self) -> None: """Clear all instructions in self. Clearing the circuits will keep the metadata and calibrations. + + .. seealso:: + :meth:`copy_empty_like` + A method to produce a new circuit with no instructions and all the same tracking of + quantum and classical typed data, but without mutating the original circuit. """ self._data.clear() self._parameter_table.clear() @@ -3007,6 +3907,28 @@ def remove_final_measurements(self, inplace: bool = True) -> Optional["QuantumCi Measurements and barriers are considered final if they are followed by no other operations (aside from other measurements or barriers.) + .. note:: + This method has rather complex behavior, particularly around the removal of newly idle + classical bits and registers. It is much more efficient to avoid adding unnecessary + classical data in the first place, rather than trying to remove it later. + + .. seealso:: + :class:`.RemoveFinalMeasurements` + A transpiler pass that removes final measurements and barriers. This does not + remove the classical data. If this is your goal, you can call that with:: + + from qiskit.circuit import QuantumCircuit + from qiskit.transpiler.passes import RemoveFinalMeasurements + + qc = QuantumCircuit(2, 2) + qc.h(0) + qc.cx(0, 1) + qc.barrier() + qc.measure([0, 1], [0, 1]) + + pass_ = RemoveFinalMeasurements() + just_bell = pass_(qc) + Args: inplace (bool): All measurements removed inplace or return new circuit. @@ -3110,7 +4032,7 @@ def from_qasm_str(qasm_str: str) -> "QuantumCircuit": @property def global_phase(self) -> ParameterValueType: - """Return the global phase of the current circuit scope in radians.""" + """The global phase of the current circuit scope in radians.""" if self._control_flow_scopes: return self._control_flow_scopes[-1].global_phase return self._global_phase @@ -5206,15 +6128,7 @@ def for_loop( ) @typing.overload - def if_test( - self, - condition: tuple[ClassicalRegister | Clbit, int], - true_body: None, - qubits: None, - clbits: None, - *, - label: str | None, - ) -> IfContext: ... + def if_test(self, condition: tuple[ClassicalRegister | Clbit, int]) -> IfContext: ... @typing.overload def if_test( diff --git a/qiskit/circuit/random/__init__.py b/qiskit/circuit/random/__init__.py index 3e3dc752d5a1..06e817bb4de8 100644 --- a/qiskit/circuit/random/__init__.py +++ b/qiskit/circuit/random/__init__.py @@ -12,4 +12,4 @@ """Method for generating random circuits.""" -from .utils import random_circuit +from .utils import random_circuit, random_clifford_circuit diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index 71809735aa8e..fc497a300cba 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017. +# (C) Copyright IBM 2017, 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 @@ -207,3 +207,72 @@ def random_circuit( qc.measure(qc.qubits, cr) return qc + + +def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): + """Generate a pseudo-random Clifford circuit. + + This function will generate a Clifford circuit by randomly selecting the chosen amount of Clifford + gates from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`. For example: + + .. plot:: + :include-source: + + from qiskit.circuit.random import random_clifford_circuit + + circ = random_clifford_circuit(num_qubits=2, num_gates=6) + circ.draw(output='mpl') + + Args: + num_qubits (int): number of quantum wires. + num_gates (int): number of gates in the circuit. + gates (list[str]): optional list of Clifford gate names to randomly sample from. + If ``"all"`` (default), use all Clifford gates in the standard library. + seed (int | np.random.Generator): sets random seed/generator (optional). + + Returns: + QuantumCircuit: constructed circuit + """ + + gates_1q = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg"] + gates_2q = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] + if gates == "all": + if num_qubits == 1: + gates = gates_1q + else: + gates = gates_1q + gates_2q + + instructions = { + "i": (standard_gates.IGate(), 1), + "x": (standard_gates.XGate(), 1), + "y": (standard_gates.YGate(), 1), + "z": (standard_gates.ZGate(), 1), + "h": (standard_gates.HGate(), 1), + "s": (standard_gates.SGate(), 1), + "sdg": (standard_gates.SdgGate(), 1), + "sx": (standard_gates.SXGate(), 1), + "sxdg": (standard_gates.SXdgGate(), 1), + "cx": (standard_gates.CXGate(), 2), + "cy": (standard_gates.CYGate(), 2), + "cz": (standard_gates.CZGate(), 2), + "swap": (standard_gates.SwapGate(), 2), + "iswap": (standard_gates.iSwapGate(), 2), + "ecr": (standard_gates.ECRGate(), 2), + "dcx": (standard_gates.DCXGate(), 2), + } + + if isinstance(seed, np.random.Generator): + rng = seed + else: + rng = np.random.default_rng(seed) + + samples = rng.choice(gates, num_gates) + + circ = QuantumCircuit(num_qubits) + + for name in samples: + gate, nqargs = instructions[name] + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + circ.append(gate, qargs, copy=False) + + return circ diff --git a/qiskit/converters/__init__.py b/qiskit/converters/__init__.py index 459b739ee011..f3d3edb5b778 100644 --- a/qiskit/converters/__init__.py +++ b/qiskit/converters/__init__.py @@ -17,12 +17,27 @@ .. currentmodule:: qiskit.converters -.. autofunction:: circuit_to_dag -.. autofunction:: dag_to_circuit +QuantumCircuit -> circuit components +==================================== + .. autofunction:: circuit_to_instruction .. autofunction:: circuit_to_gate + +QuantumCircuit <-> DagCircuit +============================= + +.. autofunction:: circuit_to_dag +.. autofunction:: dag_to_circuit + +QuantumCircuit <-> DagDependency +================================ + .. autofunction:: dagdependency_to_circuit .. autofunction:: circuit_to_dagdependency + +DagCircuit <-> DagDependency +============================ + .. autofunction:: dag_to_dagdependency .. autofunction:: dagdependency_to_dag """ diff --git a/qiskit/dagcircuit/collect_blocks.py b/qiskit/dagcircuit/collect_blocks.py index ea574536f45a..c5c7b49144f7 100644 --- a/qiskit/dagcircuit/collect_blocks.py +++ b/qiskit/dagcircuit/collect_blocks.py @@ -288,8 +288,8 @@ def run(self, block): self.group[self.find_leader(first)].append(node) blocks = [] - for index in self.leader: - if self.leader[index] == index: + for index, item in self.leader.items(): + if index == item: blocks.append(self.group[index]) return blocks diff --git a/qiskit/dagcircuit/dagnode.py b/qiskit/dagcircuit/dagnode.py index 97283c75b8fc..9f35f6eda898 100644 --- a/qiskit/dagcircuit/dagnode.py +++ b/qiskit/dagcircuit/dagnode.py @@ -14,14 +14,11 @@ """Objects to represent the information at a node in the DAGCircuit.""" from __future__ import annotations -import itertools import typing import uuid -from collections.abc import Iterable - +import qiskit._accelerate.circuit from qiskit.circuit import ( - Qubit, Clbit, ClassicalRegister, ControlFlowOp, @@ -30,7 +27,6 @@ SwitchCaseOp, ForLoopOp, Parameter, - Operation, QuantumCircuit, ) from qiskit.circuit.classical import expr @@ -39,6 +35,12 @@ from qiskit.dagcircuit import DAGCircuit +DAGNode = qiskit._accelerate.circuit.DAGNode +DAGOpNode = qiskit._accelerate.circuit.DAGOpNode +DAGInNode = qiskit._accelerate.circuit.DAGInNode +DAGOutNode = qiskit._accelerate.circuit.DAGOutNode + + def _legacy_condition_eq(cond1, cond2, bit_indices1, bit_indices2) -> bool: if cond1 is cond2 is None: return True @@ -175,158 +177,65 @@ def _for_loop_eq(node1, node2, bit_indices1, bit_indices2): _SEMANTIC_EQ_SYMMETRIC = frozenset({"barrier", "swap", "break_loop", "continue_loop"}) -class DAGNode: - """Parent class for DAGOpNode, DAGInNode, and DAGOutNode.""" - - __slots__ = ["_node_id"] - - def __init__(self, nid=-1): - """Create a node""" - self._node_id = nid - - def __lt__(self, other): - return self._node_id < other._node_id - - def __gt__(self, other): - return self._node_id > other._node_id - - def __str__(self): - # TODO is this used anywhere other than in DAG drawing? - # needs to be unique as it is what pydot uses to distinguish nodes - return str(id(self)) - - @staticmethod - def semantic_eq(node1, node2, bit_indices1, bit_indices2): - """ - Check if DAG nodes are considered equivalent, e.g., as a node_match for - :func:`rustworkx.is_isomorphic_node_match`. - - Args: - node1 (DAGOpNode, DAGInNode, DAGOutNode): A node to compare. - node2 (DAGOpNode, DAGInNode, DAGOutNode): The other node to compare. - bit_indices1 (dict): Dictionary mapping Bit instances to their index - within the circuit containing node1 - bit_indices2 (dict): Dictionary mapping Bit instances to their index - within the circuit containing node2 - - Return: - Bool: If node1 == node2 - """ - if not isinstance(node1, DAGOpNode) or not isinstance(node1, DAGOpNode): - return type(node1) is type(node2) and bit_indices1.get(node1.wire) == bit_indices2.get( - node2.wire - ) - if isinstance(node1.op, ControlFlowOp) and isinstance(node2.op, ControlFlowOp): - # While control-flow operations aren't represented natively in the DAG, we have to do - # some unpleasant dispatching and very manual handling. Once they have more first-class - # support we'll still be dispatching, but it'll look more appropriate (like the dispatch - # based on `DAGOpNode`/`DAGInNode`/`DAGOutNode` that already exists) and less like we're - # duplicating code from the `ControlFlowOp` classes. - if type(node1.op) is not type(node2.op): - return False - comparer = _SEMANTIC_EQ_CONTROL_FLOW.get(type(node1.op)) - if comparer is None: # pragma: no cover - raise RuntimeError(f"unhandled control-flow operation: {type(node1.op)}") - return comparer(node1, node2, bit_indices1, bit_indices2) - - node1_qargs = [bit_indices1[qarg] for qarg in node1.qargs] - node1_cargs = [bit_indices1[carg] for carg in node1.cargs] - - node2_qargs = [bit_indices2[qarg] for qarg in node2.qargs] - node2_cargs = [bit_indices2[carg] for carg in node2.cargs] - - # For barriers, qarg order is not significant so compare as sets - if node1.op.name == node2.op.name and node1.name in _SEMANTIC_EQ_SYMMETRIC: - node1_qargs = set(node1_qargs) - node1_cargs = set(node1_cargs) - node2_qargs = set(node2_qargs) - node2_cargs = set(node2_cargs) - - return ( - node1_qargs == node2_qargs - and node1_cargs == node2_cargs - and _legacy_condition_eq( - getattr(node1.op, "condition", None), - getattr(node2.op, "condition", None), - bit_indices1, - bit_indices2, - ) - and node1.op == node2.op +# Note: called from dag_node.rs. +def _semantic_eq(node1, node2, bit_indices1, bit_indices2): + """ + Check if DAG nodes are considered equivalent, e.g., as a node_match for + :func:`rustworkx.is_isomorphic_node_match`. + + Args: + node1 (DAGOpNode, DAGInNode, DAGOutNode): A node to compare. + node2 (DAGOpNode, DAGInNode, DAGOutNode): The other node to compare. + bit_indices1 (dict): Dictionary mapping Bit instances to their index + within the circuit containing node1 + bit_indices2 (dict): Dictionary mapping Bit instances to their index + within the circuit containing node2 + + Return: + Bool: If node1 == node2 + """ + if not isinstance(node1, DAGOpNode) or not isinstance(node1, DAGOpNode): + return type(node1) is type(node2) and bit_indices1.get(node1.wire) == bit_indices2.get( + node2.wire ) + if isinstance(node1.op, ControlFlowOp) and isinstance(node2.op, ControlFlowOp): + # While control-flow operations aren't represented natively in the DAG, we have to do + # some unpleasant dispatching and very manual handling. Once they have more first-class + # support we'll still be dispatching, but it'll look more appropriate (like the dispatch + # based on `DAGOpNode`/`DAGInNode`/`DAGOutNode` that already exists) and less like we're + # duplicating code from the `ControlFlowOp` classes. + if type(node1.op) is not type(node2.op): + return False + comparer = _SEMANTIC_EQ_CONTROL_FLOW.get(type(node1.op)) + if comparer is None: # pragma: no cover + raise RuntimeError(f"unhandled control-flow operation: {type(node1.op)}") + return comparer(node1, node2, bit_indices1, bit_indices2) + + node1_qargs = [bit_indices1[qarg] for qarg in node1.qargs] + node1_cargs = [bit_indices1[carg] for carg in node1.cargs] + + node2_qargs = [bit_indices2[qarg] for qarg in node2.qargs] + node2_cargs = [bit_indices2[carg] for carg in node2.cargs] + + # For barriers, qarg order is not significant so compare as sets + if node1.op.name == node2.op.name and node1.name in _SEMANTIC_EQ_SYMMETRIC: + node1_qargs = set(node1_qargs) + node1_cargs = set(node1_cargs) + node2_qargs = set(node2_qargs) + node2_cargs = set(node2_cargs) + + return ( + node1_qargs == node2_qargs + and node1_cargs == node2_cargs + and _legacy_condition_eq( + getattr(node1.op, "condition", None), + getattr(node2.op, "condition", None), + bit_indices1, + bit_indices2, + ) + and node1.op == node2.op + ) -class DAGOpNode(DAGNode): - """Object to represent an Instruction at a node in the DAGCircuit.""" - - __slots__ = ["op", "qargs", "cargs", "sort_key"] - - def __init__( - self, op: Operation, qargs: Iterable[Qubit] = (), cargs: Iterable[Clbit] = (), dag=None - ): - """Create an Instruction node""" - super().__init__() - self.op = op - self.qargs = tuple(qargs) - self.cargs = tuple(cargs) - if dag is not None: - cache_key = (self.qargs, self.cargs) - key = dag._key_cache.get(cache_key, None) - if key is not None: - self.sort_key = key - else: - self.sort_key = ",".join( - f"{dag.find_bit(q).index:04d}" for q in itertools.chain(*cache_key) - ) - dag._key_cache[cache_key] = self.sort_key - else: - self.sort_key = str(self.qargs) - - @property - def name(self): - """Returns the Instruction name corresponding to the op for this node""" - return self.op.name - - @name.setter - def name(self, new_name): - """Sets the Instruction name corresponding to the op for this node""" - self.op.name = new_name - - def __repr__(self): - """Returns a representation of the DAGOpNode""" - return f"DAGOpNode(op={self.op}, qargs={self.qargs}, cargs={self.cargs})" - - -class DAGInNode(DAGNode): - """Object to represent an incoming wire node in the DAGCircuit.""" - - __slots__ = ["wire", "sort_key"] - - def __init__(self, wire): - """Create an incoming node""" - super().__init__() - self.wire = wire - # TODO sort_key which is used in dagcircuit.topological_nodes - # only works as str([]) for DAGInNodes. Need to figure out why. - self.sort_key = str([]) - - def __repr__(self): - """Returns a representation of the DAGInNode""" - return f"DAGInNode(wire={self.wire})" - - -class DAGOutNode(DAGNode): - """Object to represent an outgoing wire node in the DAGCircuit.""" - - __slots__ = ["wire", "sort_key"] - - def __init__(self, wire): - """Create an outgoing node""" - super().__init__() - self.wire = wire - # TODO sort_key which is used in dagcircuit.topological_nodes - # only works as str([]) for DAGOutNodes. Need to figure out why. - self.sort_key = str([]) - - def __repr__(self): - """Returns a representation of the DAGOutNode""" - return f"DAGOutNode(wire={self.wire})" +# Bind semantic_eq from Python to Rust implementation +DAGNode.semantic_eq = staticmethod(_semantic_eq) diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index ba416dfb063b..8d3a4e9aa693 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -21,7 +21,7 @@ import dill -from qiskit.utils.parallel import parallel_map +from qiskit.utils.parallel import parallel_map, should_run_in_parallel from .base_tasks import Task, PassManagerIR from .exceptions import PassManagerError from .flow_controllers import FlowControllerLinear @@ -225,16 +225,16 @@ def callback_func(**kwargs): in_programs = [in_programs] is_list = False - if len(in_programs) == 1: - out_program = _run_workflow( - program=in_programs[0], - pass_manager=self, - callback=callback, - **kwargs, - ) - if is_list: - return [out_program] - return out_program + # If we're not going to run in parallel, we want to avoid spending time `dill` serialising + # ourselves, since that can be quite expensive. + if len(in_programs) == 1 or not should_run_in_parallel(num_processes): + out = [ + _run_workflow(program=program, pass_manager=self, callback=callback, **kwargs) + for program in in_programs + ] + if len(in_programs) == 1 and not is_list: + return out[0] + return out del callback del kwargs diff --git a/qiskit/primitives/__init__.py b/qiskit/primitives/__init__.py index 2423f3545f80..569c075b23be 100644 --- a/qiskit/primitives/__init__.py +++ b/qiskit/primitives/__init__.py @@ -417,6 +417,7 @@ DataBin PrimitiveResult PubResult + SamplerPubResult BasePrimitiveJob PrimitiveJob @@ -466,6 +467,7 @@ PubResult, EstimatorPubLike, SamplerPubLike, + SamplerPubResult, BindingsArrayLike, ObservableLike, ObservablesArrayLike, diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index 19d300c9bbf6..6736d67a214b 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -131,7 +131,6 @@ .. autoexception:: JobTimeoutError .. autoexception:: BackendConfigurationError -===================== Writing a New Backend ===================== @@ -164,7 +163,7 @@ `qiskit-aqt-provider `__ Provider -======== +-------- A provider class serves a single purpose: to get backend objects that enable executing circuits on a device or simulator. The expectation is that any @@ -195,7 +194,7 @@ def backends(self, name=None, **kwargs): method matches the required interface. The rest is up to the specific provider on how to implement. Backend -======= +------- The backend classes are the core to the provider. These classes are what provide the interface between Qiskit and the hardware or simulator that will @@ -276,8 +275,8 @@ def run(circuits, **kwargs): return MyJob(self. job_handle, job_json, circuit) -Transpiler Interface --------------------- +Backend's Transpiler Interface +------------------------------ The key piece of the :class:`~qiskit.providers.Backend` object is how it describes itself to the compiler. This is handled with the :class:`~qiskit.transpiler.Target` class which defines @@ -453,8 +452,45 @@ def get_translation_stage_plugin(self): efficient output on ``Mybackend`` the transpiler will be able to perform these custom steps without any manual user input. -Run Method ----------- +.. _providers-guide-real-time-variables: + +Real-time variables +^^^^^^^^^^^^^^^^^^^ + +The transpiler will automatically handle real-time typed classical variables (see +:mod:`qiskit.circuit.classical`) and treat the :class:`.Store` instruction as a built-in +"directive", similar to :class:`.Barrier`. No special handling from backends is necessary to permit +this. + +If your backend is *unable* to handle classical variables and storage, we recommend that you comment +on this in your documentation, and insert a check into your :meth:`~.BackendV2.run` method (see +:ref:`providers-guide-backend-run`) to eagerly reject circuits containing them. You can examine +:attr:`.QuantumCircuit.num_vars` for the presence of variables at the top level. If you accept +:ref:`control-flow operations `, you might need to recursively search the +internal :attr:`~.ControlFlowOp.blocks` of each for scope-local variables with +:attr:`.QuantumCircuit.num_declared_vars`. + +For example, a function to check for the presence of any manual storage locations, or manual stores +to memory:: + + from qiskit.circuit import Store, ControlFlowOp, QuantumCircuit + + def has_realtime_logic(circuit: QuantumCircuit) -> bool: + if circuit.num_vars: + return True + for instruction in circuit.data: + if isinstance(instruction.operation, Store): + return True + elif isinstance(instruction.operation, ControlFlowOp): + for block in instruction.operation.blocks: + if has_realtime_logic(block): + return True + return False + +.. _providers-guide-backend-run: + +Backend.run Method +------------------ Of key importance is the :meth:`~qiskit.providers.BackendV2.run` method, which is used to actually submit circuits to a device or simulator. The run method @@ -484,8 +520,8 @@ def run(self, circuits. **kwargs): job_handle = submit_to_backend(job_jsonb) return MyJob(self. job_handle, job_json, circuit) -Options -------- +Backend Options +--------------- There are often several options for a backend that control how a circuit is run. The typical example of this is something like the number of ``shots`` which is @@ -515,7 +551,7 @@ def _default_options(cls): Job -=== +--- The output from the :obj:`~qiskit.providers.BackendV2.run` method is a :class:`~qiskit.providers.JobV1` object. Each provider is expected to implement a custom job subclass that @@ -612,7 +648,7 @@ def status(self): return JobStatus.DONE Primitives -========== +---------- While not directly part of the provider interface, the :mod:`qiskit.primitives` module is tightly coupled with providers. Specifically the primitive @@ -640,12 +676,8 @@ def status(self): :class:`~.Estimator`, :class:`~.BackendSampler`, and :class:`~.BackendEstimator` can serve as references/models on how to implement these as well. -====================================== -Migrating between Backend API Versions -====================================== - -BackendV1 -> BackendV2 -====================== +Migrating from BackendV1 to BackendV2 +===================================== The :obj:`~BackendV2` class re-defined user access for most properties of a backend to make them work with native Qiskit data structures and have flatter diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 8ffc7765109c..2e551cc311e8 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -88,12 +88,6 @@ def __init__(self, configuration, provider=None, **fields): This next bit is necessary just because autosummary generally won't summarise private methods; changing that behaviour would have annoying knock-on effects through all the rest of the documentation, so instead we just hard-code the automethod directive. - - In addition to the public abstract methods, subclasses should also implement the following - private methods: - - .. automethod:: _default_options - :noindex: """ self._configuration = configuration self._options = self._default_options() diff --git a/qiskit/providers/basic_provider/__init__.py b/qiskit/providers/basic_provider/__init__.py index 48427c73fca0..4fc0f06d76af 100644 --- a/qiskit/providers/basic_provider/__init__.py +++ b/qiskit/providers/basic_provider/__init__.py @@ -27,36 +27,15 @@ backend = BasicProvider().get_backend('basic_simulator') -Simulators -========== +Classes +======= .. autosummary:: :toctree: ../stubs/ BasicSimulator - -Provider -======== - -.. autosummary:: - :toctree: ../stubs/ - BasicProvider - -Job Class -========= - -.. autosummary:: - :toctree: ../stubs/ - BasicProviderJob - -Exceptions -========== - -.. autosummary:: - :toctree: ../stubs/ - BasicProviderError """ diff --git a/qiskit/providers/basic_provider/basic_simulator.py b/qiskit/providers/basic_provider/basic_simulator.py index b03a8df7ae5a..978e1dad56fd 100644 --- a/qiskit/providers/basic_provider/basic_simulator.py +++ b/qiskit/providers/basic_provider/basic_simulator.py @@ -528,13 +528,13 @@ def run( from qiskit.compiler import assemble out_options = {} - for key in backend_options: + for key, value in backend_options.items(): if not hasattr(self.options, key): warnings.warn( "Option %s is not used by this backend" % key, UserWarning, stacklevel=2 ) else: - out_options[key] = backend_options[key] + out_options[key] = value qobj = assemble(run_input, self, **out_options) qobj_options = qobj.config self._set_options(qobj_config=qobj_options, backend_options=backend_options) diff --git a/qiskit/providers/fake_provider/__init__.py b/qiskit/providers/fake_provider/__init__.py index 00dadd2ad254..9526793f0e10 100644 --- a/qiskit/providers/fake_provider/__init__.py +++ b/qiskit/providers/fake_provider/__init__.py @@ -24,7 +24,7 @@ useful for testing the transpiler and other backend-facing functionality. Example Usage -============= +------------- Here is an example of using a simulated backend for transpilation and running. diff --git a/qiskit/providers/models/__init__.py b/qiskit/providers/models/__init__.py index a69038eb78c7..bf90a9d16c0e 100644 --- a/qiskit/providers/models/__init__.py +++ b/qiskit/providers/models/__init__.py @@ -19,8 +19,8 @@ Qiskit schema-conformant objects used by the backends and providers. -Backend Objects -=============== +Classes +======= .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/providers/models/backendconfiguration.py b/qiskit/providers/models/backendconfiguration.py index e346e293a38b..ebd0a6d9bbb6 100644 --- a/qiskit/providers/models/backendconfiguration.py +++ b/qiskit/providers/models/backendconfiguration.py @@ -892,9 +892,9 @@ def get_qubit_channels(self, qubit: Union[int, Iterable[int]]) -> List[Channel]: channels = set() try: if isinstance(qubit, int): - for key in self._qubit_channel_map.keys(): + for key, value in self._qubit_channel_map.items(): if qubit in key: - channels.update(self._qubit_channel_map[key]) + channels.update(value) if len(channels) == 0: raise KeyError elif isinstance(qubit, list): diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py index 7a5b7a260354..659af518a2b7 100644 --- a/qiskit/providers/options.py +++ b/qiskit/providers/options.py @@ -229,28 +229,28 @@ def set_validator(self, field, validator_value): f"{type(validator_value)} is not a valid validator type, it " "must be a tuple, list, or class/type" ) - self.validator[field] = validator_value + self.validator[field] = validator_value # pylint: disable=unsupported-assignment-operation def update_options(self, **fields): """Update options with kwargs""" - for field in fields: - field_validator = self.validator.get(field, None) + for field_name, field in fields.items(): + field_validator = self.validator.get(field_name, None) if isinstance(field_validator, tuple): - if fields[field] > field_validator[1] or fields[field] < field_validator[0]: + if field > field_validator[1] or field < field_validator[0]: raise ValueError( - f"Specified value for '{field}' is not a valid value, " + f"Specified value for '{field_name}' is not a valid value, " f"must be >={field_validator[0]} or <={field_validator[1]}" ) elif isinstance(field_validator, list): - if fields[field] not in field_validator: + if field not in field_validator: raise ValueError( - f"Specified value for {field} is not a valid choice, " + f"Specified value for {field_name} is not a valid choice, " f"must be one of {field_validator}" ) elif isinstance(field_validator, type): - if not isinstance(fields[field], field_validator): + if not isinstance(field, field_validator): raise TypeError( - f"Specified value for {field} is not of required type {field_validator}" + f"Specified value for {field_name} is not of required type {field_validator}" ) self._fields.update(fields) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index b7bdbe85c192..8767e8c4e93a 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -74,8 +74,8 @@ The builder initializes a :class:`.pulse.Schedule`, ``pulse_prog`` and then begins to construct the program within the context. The output pulse -schedule will survive after the context is exited and can be transpiled and executed like a -normal Qiskit schedule using ``backend.run(transpile(pulse_prog, backend))``. +schedule will survive after the context is exited and can be used like a +normal Qiskit schedule. Pulse programming has a simple imperative style. This leaves the programmer to worry about the raw experimental physics of pulse programming and not diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 9041dbc19510..b076bcf56cb0 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -677,8 +677,8 @@ def __eq__(self, other: object) -> bool: if not np.isclose(complex_amp1, complex_amp2): return False - for key in self.parameters: - if key not in ["amp", "angle"] and self.parameters[key] != other.parameters[key]: + for key, value in self.parameters.items(): + if key not in ["amp", "angle"] and value != other.parameters[key]: return False return True diff --git a/qiskit/pulse/parser.py b/qiskit/pulse/parser.py index a9e752f562e5..8e31faebf77a 100644 --- a/qiskit/pulse/parser.py +++ b/qiskit/pulse/parser.py @@ -120,7 +120,7 @@ def __call__(self, *args, **kwargs) -> complex | ast.Expression | PulseExpressio if kwargs: for key, val in kwargs.items(): if key in self.params: - if key not in self._locals_dict.keys(): + if key not in self._locals_dict: self._locals_dict[key] = val else: raise PulseError( @@ -272,7 +272,7 @@ def visit_Call(self, node: ast.Call) -> ast.Call | ast.Constant: node = copy.copy(node) node.args = [self.visit(arg) for arg in node.args] if all(isinstance(arg, ast.Constant) for arg in node.args): - if node.func.id not in self._math_ops.keys(): + if node.func.id not in self._math_ops: raise PulseError("Function %s is not supported." % node.func.id) _args = [arg.value for arg in node.args] _val = self._math_ops[node.func.id](*_args) diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 77e811100f37..80c332aaab4a 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -234,7 +234,7 @@ def _convert_set_frequency( "name": "setf", "t0": time_offset + instruction.start_time, "ch": instruction.channel.name, - "frequency": instruction.frequency / 1e9, + "frequency": instruction.frequency / 10**9, } return self._qobj_model(**command_dict) @@ -257,7 +257,7 @@ def _convert_shift_frequency( "name": "shiftf", "t0": time_offset + instruction.start_time, "ch": instruction.channel.name, - "frequency": instruction.frequency / 1e9, + "frequency": instruction.frequency / 10**9, } return self._qobj_model(**command_dict) @@ -746,7 +746,7 @@ def _convert_setf( .. note:: We assume frequency value is expressed in string with "GHz". - Operand value is thus scaled by a factor of 1e9. + Operand value is thus scaled by a factor of 10^9. Args: instruction: SetFrequency qobj instruction @@ -755,7 +755,7 @@ def _convert_setf( Qiskit Pulse set frequency instructions """ channel = self.get_channel(instruction.ch) - frequency = self.disassemble_value(instruction.frequency) * 1e9 + frequency = self.disassemble_value(instruction.frequency) * 10**9 yield instructions.SetFrequency(frequency, channel) @@ -768,7 +768,7 @@ def _convert_shiftf( .. note:: We assume frequency value is expressed in string with "GHz". - Operand value is thus scaled by a factor of 1e9. + Operand value is thus scaled by a factor of 10^9. Args: instruction: ShiftFrequency qobj instruction @@ -777,7 +777,7 @@ def _convert_shiftf( Qiskit Pulse shift frequency schedule instructions """ channel = self.get_channel(instruction.ch) - frequency = self.disassemble_value(instruction.frequency) * 1e9 + frequency = self.disassemble_value(instruction.frequency) * 10**9 yield instructions.ShiftFrequency(frequency, channel) diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index d7275bcd62f4..4e7769106fc6 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -11,9 +11,9 @@ # that they have been altered from the originals. """ -########################################################### +===================================== QPY serialization (:mod:`qiskit.qpy`) -########################################################### +===================================== .. currentmodule:: qiskit.qpy @@ -32,9 +32,8 @@ version (it is also `potentially insecure `__). -********* -Using QPY -********* +Basic Usage +=========== Using QPY is defined to be straightforward and mirror the user API of the serializers in Python's standard library, ``pickle`` and ``json``. There are @@ -248,9 +247,8 @@ .. _qpy_format: -********** QPY Format -********** +========== The QPY serialization format is a portable cross-platform binary serialization format for :class:`~qiskit.circuit.QuantumCircuit` objects in Qiskit. The basic @@ -303,14 +301,14 @@ .. _qpy_version_12: Version 12 -========== +---------- Version 12 adds support for: * circuits containing memory-owning :class:`.expr.Var` variables. Changes to HEADER ------------------ +~~~~~~~~~~~~~~~~~ The HEADER struct for an individual circuit has added three ``uint32_t`` counts of the input, captured and locally declared variables in the circuit. The new form looks like: @@ -336,7 +334,7 @@ EXPR_VAR_DECLARATION --------------------- +~~~~~~~~~~~~~~~~~~~~ An ``EXPR_VAR_DECLARATION`` defines an :class:`.expr.Var` instance that is standalone; that is, it represents a self-owned memory location rather than wrapping a :class:`.Clbit` or @@ -367,7 +365,7 @@ Changes to EXPR_VAR -------------------- +~~~~~~~~~~~~~~~~~~~ The EXPR_VAR variable has gained a new type code and payload, in addition to the pre-existing ones: @@ -400,7 +398,7 @@ .. _qpy_version_11: Version 11 -========== +---------- Version 11 is identical to Version 10 except for the following. First, the names in the CUSTOM_INSTRUCTION blocks @@ -418,7 +416,7 @@ .. _modifier_qpy: MODIFIER --------- +~~~~~~~~ This represents :class:`~qiskit.circuit.annotated_operation.Modifier` @@ -441,7 +439,7 @@ .. _qpy_version_10: Version 10 -========== +---------- Version 10 adds support for: @@ -454,7 +452,7 @@ encoding and ``e`` refers to symengine encoding. Changes to FILE_HEADER ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ The contents of FILE_HEADER after V10 are defined as a C struct as: @@ -470,7 +468,7 @@ } FILE_HEADER_V10; Changes to LAYOUT ------------------ +~~~~~~~~~~~~~~~~~ The ``LAYOUT`` struct is updated to have an additional ``input_qubit_count`` field. With version 10 the ``LAYOUT`` struct is now: @@ -493,14 +491,14 @@ .. _qpy_version_9: Version 9 -========= +--------- Version 9 adds support for classical :class:`~.expr.Expr` nodes and their associated :class:`~.types.Type`\\ s. EXPRESSION ----------- +~~~~~~~~~~ An :class:`~.expr.Expr` node is represented by a stream of variable-width data. A node itself is represented by (in order in the byte stream): @@ -532,7 +530,7 @@ EXPR_TYPE ---------- +~~~~~~~~~ A :class:`~.types.Type` is encoded by a single-byte ASCII ``char`` that encodes the kind of type, followed by a payload that varies depending on the type. The defined codes are: @@ -547,7 +545,7 @@ EXPR_VAR --------- +~~~~~~~~ This represents a runtime variable of a :class:`~.expr.Var` node. These are a type code, followed by a type-code-specific payload: @@ -564,7 +562,7 @@ EXPR_VALUE ----------- +~~~~~~~~~~ This represents a literal object in the classical type system, such as an integer. Currently there are very few such literals. These are encoded as a type code, followed by a type-code-specific @@ -582,7 +580,7 @@ Changes to INSTRUCTION ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ To support the use of :class:`~.expr.Expr` nodes in the fields :attr:`.IfElseOp.condition`, :attr:`.WhileLoopOp.condition` and :attr:`.SwitchCaseOp.target`, the INSTRUCTION struct is changed @@ -629,7 +627,7 @@ Changes to INSTRUCTION_PARAM ----------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A new type code ``x`` is added that defines an EXPRESSION parameter. @@ -637,7 +635,7 @@ .. _qpy_version_8: Version 8 -========= +--------- Version 8 adds support for handling a :class:`~.TranspileLayout` stored in the :attr:`.QuantumCircuit.layout` attribute. In version 8 immediately following the @@ -646,7 +644,7 @@ :class:`~.TranspileLayout` class. LAYOUT ------- +~~~~~~ .. code-block:: c @@ -668,7 +666,7 @@ :attr:`.TranspileLayout.initial_layout` attribute. INITIAL_LAYOUT_BIT ------------------- +~~~~~~~~~~~~~~~~~~ .. code-block:: c @@ -694,7 +692,7 @@ .. _qpy_version_7: Version 7 -========= +--------- Version 7 adds support for :class:`.~Reference` instruction and serialization of a :class:`.~ScheduleBlock` program while keeping its reference to subroutines:: @@ -740,7 +738,7 @@ .. _qpy_version_6: Version 6 -========= +--------- Version 6 adds support for :class:`.~ScalableSymbolicPulse`. These objects are saved and read like `SymbolicPulse` objects, and the class name is added to the data to correctly handle @@ -767,7 +765,7 @@ .. _qpy_version_5: Version 5 -========= +--------- Version 5 changes from :ref:`qpy_version_4` by adding support for :class:`.~ScheduleBlock` and changing two payloads the INSTRUCTION metadata payload and the CUSTOM_INSTRUCTION block. @@ -802,7 +800,7 @@ .. _qpy_schedule_block: SCHEDULE_BLOCK --------------- +~~~~~~~~~~~~~~ :class:`~.ScheduleBlock` is first supported in QPY Version 5. This allows users to save pulse programs in the QPY binary format as follows: @@ -827,7 +825,7 @@ .. _qpy_schedule_block_header: SCHEDULE_BLOCK_HEADER ---------------------- +~~~~~~~~~~~~~~~~~~~~~ :class:`~.ScheduleBlock` block starts with the following header: @@ -846,7 +844,7 @@ .. _qpy_schedule_alignments: SCHEDULE_BLOCK_ALIGNMENTS -------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~ Then, alignment context of the schedule block starts with ``char`` representing the supported context type followed by the :ref:`qpy_sequence` block representing @@ -864,7 +862,7 @@ .. _qpy_schedule_instructions: SCHEDULE_BLOCK_INSTRUCTIONS ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ This alignment block is further followed by ``num_element`` length of block elements which may consist of nested schedule blocks and schedule instructions. @@ -889,7 +887,7 @@ .. _qpy_schedule_operands: SCHEDULE_BLOCK_OPERANDS ------------------------ +~~~~~~~~~~~~~~~~~~~~~~~ The operands of these instances can be serialized through the standard QPY value serialization mechanism, however there are special object types that only appear in the schedule operands. @@ -906,7 +904,7 @@ .. _qpy_schedule_channel: CHANNEL -------- +~~~~~~~ Channel block starts with channel subtype ``char`` that maps an object data to :class:`~qiskit.pulse.channels.Channel` subclass. Mapping is defined as follows: @@ -923,7 +921,7 @@ .. _qpy_schedule_waveform: Waveform --------- +~~~~~~~~ Waveform block starts with WAVEFORM header: @@ -945,7 +943,7 @@ .. _qpy_schedule_symbolic_pulse: SymbolicPulse -------------- +~~~~~~~~~~~~~ SymbolicPulse block starts with SYMBOLIC_PULSE header: @@ -979,7 +977,7 @@ .. _qpy_mapping: MAPPING -------- +~~~~~~~ The MAPPING is a representation for arbitrary mapping object. This is a fixed length :ref:`qpy_sequence` of key-value pair represented by the MAP_ITEM payload. @@ -1001,7 +999,7 @@ .. _qpy_circuit_calibrations: CIRCUIT_CALIBRATIONS --------------------- +~~~~~~~~~~~~~~~~~~~~ The CIRCUIT_CALIBRATIONS block is a dictionary to define pulse calibrations of the custom instruction set. This block starts with the following CALIBRATION header: @@ -1036,7 +1034,7 @@ .. _qpy_instruction_v5: INSTRUCTION ------------ +~~~~~~~~~~~ The INSTRUCTION block was modified to add two new fields ``num_ctrl_qubits`` and ``ctrl_state`` which are used to model the :attr:`.ControlledGate.num_ctrl_qubits` and @@ -1062,7 +1060,7 @@ :ref:`qpy_instructions` for the details of the full payload. CUSTOM_INSTRUCTION ------------------- +~~~~~~~~~~~~~~~~~~ The CUSTOM_INSTRUCTION block in QPY version 5 adds a new field ``base_gate_size`` which is used to define the size of the @@ -1105,7 +1103,7 @@ .. _qpy_version_4: Version 4 -========= +--------- Version 4 is identical to :ref:`qpy_version_3` except that it adds 2 new type strings to the INSTRUCTION_PARAM struct, ``z`` to represent ``None`` (which is encoded as @@ -1135,7 +1133,7 @@ .. _qpy_range_pack: RANGE ------ +~~~~~ A RANGE is a representation of a ``range`` object. It is defined as: @@ -1150,7 +1148,7 @@ .. _qpy_sequence: SEQUENCE --------- +~~~~~~~~ A SEQUENCE is a representation of an arbitrary sequence object. As sequence are just fixed length containers of arbitrary python objects their QPY can't fully represent any sequence, @@ -1172,7 +1170,7 @@ .. _qpy_version_3: Version 3 -========= +--------- Version 3 of the QPY format is identical to :ref:`qpy_version_2` except that it defines a struct format to represent a :class:`~qiskit.circuit.library.PauliEvolutionGate` @@ -1187,7 +1185,7 @@ .. _pauli_evo_qpy: PAULI_EVOLUTION ---------------- +~~~~~~~~~~~~~~~ This represents the high level :class:`~qiskit.circuit.library.PauliEvolutionGate` @@ -1215,7 +1213,7 @@ .. _qpy_pauli_sum_op: SPARSE_PAULI_OP_LIST_ELEM -------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~ This represents an instance of :class:`.SparsePauliOp`. @@ -1239,7 +1237,7 @@ .. _qpy_param_vector: PARAMETER_VECTOR_ELEMENT ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ A PARAMETER_VECTOR_ELEMENT represents a :class:`~qiskit.circuit.ParameterVectorElement` object the data for a INSTRUCTION_PARAM. The contents of the PARAMETER_VECTOR_ELEMENT are @@ -1261,7 +1259,7 @@ PARAMETER_EXPR --------------- +~~~~~~~~~~~~~~ Additionally, since QPY format version v3 distinguishes between a :class:`~qiskit.circuit.Parameter` and :class:`~qiskit.circuit.ParameterVectorElement` @@ -1315,14 +1313,14 @@ .. _qpy_version_2: Version 2 -========= +--------- Version 2 of the QPY format is identical to version 1 except for the HEADER section is slightly different. You can refer to the :ref:`qpy_version_1` section for the details on the rest of the payload format. HEADER ------- +~~~~~~ The contents of HEADER are defined as a C struct are: @@ -1352,10 +1350,10 @@ .. _qpy_version_1: Version 1 -========= +--------- HEADER ------- +~~~~~~ The contents of HEADER as defined as a C struct are: @@ -1375,7 +1373,7 @@ of the circuit. METADATA --------- +~~~~~~~~ The METADATA field is a UTF8 encoded JSON string. After reading the HEADER (which is a fixed size at the start of the QPY file) and the ``name`` string @@ -1385,7 +1383,7 @@ .. _qpy_registers: REGISTERS ---------- +~~~~~~~~~ The contents of REGISTERS is a number of REGISTER object. If num_registers is > 0 then after reading METADATA you read that number of REGISTER structs defined @@ -1435,7 +1433,7 @@ .. _qpy_custom_definition: CUSTOM_DEFINITIONS ------------------- +~~~~~~~~~~~~~~~~~~ This section specifies custom definitions for any of the instructions in the circuit. @@ -1475,7 +1473,7 @@ .. _qpy_instructions: INSTRUCTIONS ------------- +~~~~~~~~~~~~ The contents of INSTRUCTIONS is a list of INSTRUCTION metadata objects @@ -1551,7 +1549,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom .. _qpy_param_struct: PARAMETER ---------- +~~~~~~~~~ A PARAMETER represents a :class:`~qiskit.circuit.Parameter` object the data for a INSTRUCTION_PARAM. The contents of the PARAMETER are defined as: @@ -1569,7 +1567,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom .. _qpy_param_expr: PARAMETER_EXPR --------------- +~~~~~~~~~~~~~~ A PARAMETER_EXPR represents a :class:`~qiskit.circuit.ParameterExpression` object that the data for an INSTRUCTION_PARAM. The contents of a PARAMETER_EXPR @@ -1608,7 +1606,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom .. _qpy_complex: COMPLEX -------- +~~~~~~~ When representing a double precision complex value in QPY the following struct is used: diff --git a/qiskit/quantum_info/operators/dihedral/random.py b/qiskit/quantum_info/operators/dihedral/random.py index f339cf983771..4331d618d73d 100644 --- a/qiskit/quantum_info/operators/dihedral/random.py +++ b/qiskit/quantum_info/operators/dihedral/random.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -49,7 +49,9 @@ def random_cnotdihedral(num_qubits, seed=None): # Random affine function # Random invertible binary matrix - from qiskit.synthesis.linear import random_invertible_binary_matrix + from qiskit.synthesis.linear import ( # pylint: disable=cyclic-import + random_invertible_binary_matrix, + ) linear = random_invertible_binary_matrix(num_qubits, seed=rng) elem.linear = linear diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index e1bcfa29ebcb..8187c1ee41e6 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -144,13 +144,13 @@ class initialization (``Pauli('-iXYZ')``). A ``Pauli`` object can be .. code-block:: python - p = Pauli('-iXYZ') + P = Pauli('-iXYZ') print('P[0] =', repr(P[0])) print('P[1] =', repr(P[1])) print('P[2] =', repr(P[2])) print('P[:] =', repr(P[:])) - print('P[::-1] =, repr(P[::-1])) + print('P[::-1] =', repr(P[::-1])) """ # Set the max Pauli string size before truncation @@ -736,8 +736,11 @@ def apply_layout( n_qubits = num_qubits if layout is None: layout = list(range(self.num_qubits)) - elif any(x >= n_qubits for x in layout): - raise QiskitError("Provided layout contains indices outside the number of qubits.") + else: + if any(x < 0 or x >= n_qubits for x in layout): + raise QiskitError("Provided layout contains indices outside the number of qubits.") + if len(set(layout)) != len(layout): + raise QiskitError("Provided layout contains duplicate indices.") new_op = type(self)("I" * n_qubits) return new_op.compose(self, qargs=layout) diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index dffe5b2396b2..440a0319c33a 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -1139,7 +1139,6 @@ def apply_layout( specified will be applied without any expansion. If layout is None, the operator will be expanded to the given number of qubits. - Returns: A new :class:`.SparsePauliOp` with the provided layout applied """ @@ -1159,10 +1158,15 @@ def apply_layout( f"applied to a {n_qubits} qubit operator" ) n_qubits = num_qubits - if layout is not None and any(x >= n_qubits for x in layout): - raise QiskitError("Provided layout contains indices outside the number of qubits.") if layout is None: layout = list(range(self.num_qubits)) + else: + if any(x < 0 or x >= n_qubits for x in layout): + raise QiskitError("Provided layout contains indices outside the number of qubits.") + if len(set(layout)) != len(layout): + raise QiskitError("Provided layout contains duplicate indices.") + if self.num_qubits == 0: + return type(self)(["I" * n_qubits] * self.size, self.coeffs) new_op = type(self)("I" * n_qubits) return new_op.compose(self, qargs=layout) diff --git a/qiskit/quantum_info/quaternion.py b/qiskit/quantum_info/quaternion.py index 22508b5ceb1e..69b9b61c8f99 100644 --- a/qiskit/quantum_info/quaternion.py +++ b/qiskit/quantum_info/quaternion.py @@ -43,7 +43,7 @@ def __mul__(self, r): out_data[3] = r(0) * q(3) - r(1) * q(2) + r(2) * q(1) + r(3) * q(0) return Quaternion(out_data) else: - raise Exception("Multiplication by other not supported.") + return NotImplemented def norm(self): """Norm of quaternion.""" diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py index 7f616bcff79f..4ae16c32bf54 100644 --- a/qiskit/quantum_info/states/stabilizerstate.py +++ b/qiskit/quantum_info/states/stabilizerstate.py @@ -386,8 +386,17 @@ def probabilities(self, qargs: None | list = None, decimals: None | int = None) return probs - def probabilities_dict(self, qargs: None | list = None, decimals: None | int = None) -> dict: - """Return the subsystem measurement probability dictionary. + def probabilities_dict_from_bitstring( + self, + outcome_bitstring: str, + qargs: None | list = None, + decimals: None | int = None, + ) -> dict[str, float]: + """Return the subsystem measurement probability dictionary utilizing + a targeted outcome_bitstring to perform the measurement for. This + will calculate a probability for only a single targeted + outcome_bitstring value, giving a performance boost over calculating + all possible outcomes. Measurement probabilities are with respect to measurement in the computation (diagonal) basis. @@ -398,30 +407,44 @@ def probabilities_dict(self, qargs: None | list = None, decimals: None | int = N inserted between integers so that subsystems can be distinguished. Args: + outcome_bitstring (None or str): targeted outcome bitstring + to perform a measurement calculation for, this will significantly + reduce the number of calculation performed (Default: None) qargs (None or list): subsystems to return probabilities for, - if None return for all subsystems (Default: None). + if None return for all subsystems (Default: None). decimals (None or int): the number of decimal places to round - values. If None no rounding is done (Default: None). + values. If None no rounding is done (Default: None) Returns: - dict: The measurement probabilities in dict (ket) form. + dict[str, float]: The measurement probabilities in dict (ket) form. """ - if qargs is None: - qubits = range(self.clifford.num_qubits) - else: - qubits = qargs + return self._get_probabilities_dict( + outcome_bitstring=outcome_bitstring, qargs=qargs, decimals=decimals + ) - outcome = ["X"] * len(qubits) - outcome_prob = 1.0 - probs = {} # probabilities dictionary + def probabilities_dict( + self, qargs: None | list = None, decimals: None | int = None + ) -> dict[str, float]: + """Return the subsystem measurement probability dictionary. - self._get_probabilities(qubits, outcome, outcome_prob, probs) + Measurement probabilities are with respect to measurement in the + computation (diagonal) basis. - if decimals is not None: - for key, value in probs.items(): - probs[key] = round(value, decimals) + This dictionary representation uses a Ket-like notation where the + dictionary keys are qudit strings for the subsystem basis vectors. + If any subsystem has a dimension greater than 10 comma delimiters are + inserted between integers so that subsystems can be distinguished. - return probs + Args: + qargs (None or list): subsystems to return probabilities for, + if None return for all subsystems (Default: None). + decimals (None or int): the number of decimal places to round + values. If None no rounding is done (Default: None). + + Returns: + dict: The measurement probabilities in dict (key) form. + """ + return self._get_probabilities_dict(outcome_bitstring=None, qargs=qargs, decimals=decimals) def reset(self, qargs: list | None = None) -> StabilizerState: """Reset state or subsystems to the 0-state. @@ -644,22 +667,48 @@ def _rowsum_deterministic(clifford, aux_pauli, row): # ----------------------------------------------------------------------- # Helper functions for calculating the probabilities # ----------------------------------------------------------------------- - def _get_probabilities(self, qubits, outcome, outcome_prob, probs): - """Recursive helper function for calculating the probabilities""" + def _get_probabilities( + self, + qubits: range, + outcome: list[str], + outcome_prob: float, + probs: dict[str, float], + outcome_bitstring: str = None, + ): + """Recursive helper function for calculating the probabilities - qubit_for_branching = -1 - ret = self.copy() + Args: + qubits (range): range of qubits + outcome (list[str]): outcome being built + outcome_prob (float): probabilitiy of the outcome + probs (dict[str, float]): holds the outcomes and probabilitiy results + outcome_bitstring (str): target outcome to measure which reduces measurements, None + if not targeting a specific target + """ + qubit_for_branching: int = -1 + ret: StabilizerState = self.copy() + + # Find outcomes for each qubit for i in range(len(qubits)): - qubit = qubits[len(qubits) - i - 1] if outcome[i] == "X": - is_deterministic = not any(ret.clifford.stab_x[:, qubit]) - if is_deterministic: - single_qubit_outcome = ret._measure_and_update(qubit, 0) - if single_qubit_outcome: - outcome[i] = "1" + # Retrieve the qubit for the current measurement + qubit = qubits[(len(qubits) - i - 1)] + # Determine if the probabilitiy is deterministic + if not any(ret.clifford.stab_x[:, qubit]): + single_qubit_outcome: np.int64 = ret._measure_and_update(qubit, 0) + if outcome_bitstring is None or ( + int(outcome_bitstring[i]) == single_qubit_outcome + ): + # No outcome_bitstring target, or using outcome_bitstring target and + # the single_qubit_outcome equals the desired outcome_bitstring target value, + # then use current outcome_prob value + outcome[i] = str(single_qubit_outcome) else: - outcome[i] = "0" + # If the single_qubit_outcome does not equal the outcome_bitsring target + # then we know that the probability will be 0 + outcome[i] = str(outcome_bitstring[i]) + outcome_prob = 0 else: qubit_for_branching = i @@ -668,15 +717,57 @@ def _get_probabilities(self, qubits, outcome, outcome_prob, probs): probs[str_outcome] = outcome_prob return - for single_qubit_outcome in range(0, 2): + for single_qubit_outcome in ( + range(0, 2) + if (outcome_bitstring is None) + else [int(outcome_bitstring[qubit_for_branching])] + ): new_outcome = outcome.copy() - if single_qubit_outcome: - new_outcome[qubit_for_branching] = "1" - else: - new_outcome[qubit_for_branching] = "0" + new_outcome[qubit_for_branching] = str(single_qubit_outcome) stab_cpy = ret.copy() stab_cpy._measure_and_update( - qubits[len(qubits) - qubit_for_branching - 1], single_qubit_outcome + qubits[(len(qubits) - qubit_for_branching - 1)], single_qubit_outcome + ) + stab_cpy._get_probabilities( + qubits, new_outcome, (0.5 * outcome_prob), probs, outcome_bitstring ) - stab_cpy._get_probabilities(qubits, new_outcome, 0.5 * outcome_prob, probs) + + def _get_probabilities_dict( + self, + outcome_bitstring: None | str = None, + qargs: None | list = None, + decimals: None | int = None, + ) -> dict[str, float]: + """Helper Function for calculating the subsystem measurement probability dictionary. + When the targeted outcome_bitstring value is set, then only the single outcome_bitstring + probability will be calculated. + + Args: + outcome_bitstring (None or str): targeted outcome bitstring + to perform a measurement calculation for, this will significantly + reduce the number of calculation performed (Default: None) + qargs (None or list): subsystems to return probabilities for, + if None return for all subsystems (Default: None). + decimals (None or int): the number of decimal places to round + values. If None no rounding is done (Default: None). + + Returns: + dict: The measurement probabilities in dict (key) form. + """ + if qargs is None: + qubits = range(self.clifford.num_qubits) + else: + qubits = qargs + + outcome = ["X"] * len(qubits) + outcome_prob = 1.0 + probs: dict[str, float] = {} # Probabilities dict to return with the measured values + + self._get_probabilities(qubits, outcome, outcome_prob, probs, outcome_bitstring) + + if decimals is not None: + for key, value in probs.items(): + probs[key] = round(value, decimals) + + return probs diff --git a/qiskit/result/__init__.py b/qiskit/result/__init__.py index 08b43f704939..2eaa7803c5fd 100644 --- a/qiskit/result/__init__.py +++ b/qiskit/result/__init__.py @@ -17,6 +17,9 @@ .. currentmodule:: qiskit.result +Core classes +============ + .. autosummary:: :toctree: ../stubs/ @@ -24,6 +27,9 @@ ResultError Counts +Marginalization +=============== + .. autofunction:: marginal_counts .. autofunction:: marginal_distribution .. autofunction:: marginal_memory diff --git a/qiskit/result/models.py b/qiskit/result/models.py index 07286148f886..992810196713 100644 --- a/qiskit/result/models.py +++ b/qiskit/result/models.py @@ -171,11 +171,11 @@ def __repr__(self): out += ", seed=%s" % self.seed if hasattr(self, "meas_return"): out += ", meas_return=%s" % self.meas_return - for key in self._metadata: - if isinstance(self._metadata[key], str): - value_str = "'%s'" % self._metadata[key] + for key, value in self._metadata.items(): + if isinstance(value, str): + value_str = "'%s'" % value else: - value_str = repr(self._metadata[key]) + value_str = repr(value) out += f", {key}={value_str}" out += ")" return out diff --git a/qiskit/result/result.py b/qiskit/result/result.py index d99be996080e..c1792de56ae0 100644 --- a/qiskit/result/result.py +++ b/qiskit/result/result.py @@ -81,11 +81,11 @@ def __repr__(self): ) ) out += f", date={self.date}, status={self.status}, header={self.header}" - for key in self._metadata: - if isinstance(self._metadata[key], str): - value_str = "'%s'" % self._metadata[key] + for key, value in self._metadata.items(): + if isinstance(value, str): + value_str = "'%s'" % value else: - value_str = repr(self._metadata[key]) + value_str = repr(value) out += f", {key}={value_str}" out += ")" return out diff --git a/qiskit/scheduler/__init__.py b/qiskit/scheduler/__init__.py index b33ececf5d65..7062e01a941e 100644 --- a/qiskit/scheduler/__init__.py +++ b/qiskit/scheduler/__init__.py @@ -19,13 +19,22 @@ A circuit scheduler compiles a circuit program to a pulse program. +Core API +======== + .. autoclass:: ScheduleConfig .. currentmodule:: qiskit.scheduler.schedule_circuit .. autofunction:: schedule_circuit .. currentmodule:: qiskit.scheduler -.. automodule:: qiskit.scheduler.methods +Pulse scheduling methods +======================== + +.. currentmodule:: qiskit.scheduler.methods +.. autofunction:: as_soon_as_possible +.. autofunction:: as_late_as_possible +.. currentmodule:: qiskit.scheduler """ from qiskit.scheduler import schedule_circuit from qiskit.scheduler.config import ScheduleConfig diff --git a/qiskit/scheduler/methods/__init__.py b/qiskit/scheduler/methods/__init__.py index 1fe4b301b7ab..6df887d54995 100644 --- a/qiskit/scheduler/methods/__init__.py +++ b/qiskit/scheduler/methods/__init__.py @@ -10,13 +10,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -.. currentmodule:: qiskit.scheduler.methods - -Pulse scheduling methods. - -.. autofunction:: as_soon_as_possible -.. autofunction:: as_late_as_possible -""" +"""Scheduling methods.""" from qiskit.scheduler.methods.basic import as_soon_as_possible, as_late_as_possible diff --git a/qiskit/synthesis/clifford/clifford_decompose_bm.py b/qiskit/synthesis/clifford/clifford_decompose_bm.py index cbc54f16bb0c..4800890a90d3 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_bm.py +++ b/qiskit/synthesis/clifford/clifford_decompose_bm.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021, 2022. +# (C) Copyright IBM 2021, 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 @@ -76,11 +76,11 @@ def synth_clifford_bm(clifford: Clifford) -> QuantumCircuit: pos = [qubit, qubit + num_qubits] circ = _decompose_clifford_1q(clifford.tableau[pos][:, pos + [-1]]) if len(circ) > 0: - ret_circ.append(circ, [qubit]) + ret_circ.append(circ, [qubit], copy=False) # Add the inverse of the 2-qubit reductions circuit if len(inv_circuit) > 0: - ret_circ.append(inv_circuit.inverse(), range(num_qubits)) + ret_circ.append(inv_circuit.inverse(), range(num_qubits), copy=False) return ret_circ.decompose() @@ -192,7 +192,7 @@ def _cx_cost(clifford): return _cx_cost2(clifford) if clifford.num_qubits == 3: return _cx_cost3(clifford) - raise Exception("No Clifford CX cost function for num_qubits > 3.") + raise RuntimeError("No Clifford CX cost function for num_qubits > 3.") def _rank2(a, b, c, d): diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index f1a7c5cce137..2fc9ca5bdb29 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -137,32 +137,32 @@ def synth_clifford_layers( cz_func_reverse_qubits=cz_func_reverse_qubits, ) - layeredCircuit.append(S2_circ, qubit_list) + layeredCircuit.append(S2_circ, qubit_list, copy=False) if cx_cz_synth_func is None: - layeredCircuit.append(CZ2_circ, qubit_list) + layeredCircuit.append(CZ2_circ, qubit_list, copy=False) CXinv = CX_circ.copy().inverse() - layeredCircuit.append(CXinv, qubit_list) + layeredCircuit.append(CXinv, qubit_list, copy=False) else: # note that CZ2_circ is None and built into the CX_circ when # cx_cz_synth_func is not None - layeredCircuit.append(CX_circ, qubit_list) + layeredCircuit.append(CX_circ, qubit_list, copy=False) - layeredCircuit.append(H2_circ, qubit_list) - layeredCircuit.append(S1_circ, qubit_list) - layeredCircuit.append(CZ1_circ, qubit_list) + layeredCircuit.append(H2_circ, qubit_list, copy=False) + layeredCircuit.append(S1_circ, qubit_list, copy=False) + layeredCircuit.append(CZ1_circ, qubit_list, copy=False) if cz_func_reverse_qubits: H1_circ = H1_circ.reverse_bits() - layeredCircuit.append(H1_circ, qubit_list) + layeredCircuit.append(H1_circ, qubit_list, copy=False) # Add Pauli layer to fix the Clifford phase signs clifford_target = Clifford(layeredCircuit) pauli_circ = _calc_pauli_diff(cliff, clifford_target) - layeredCircuit.append(pauli_circ, qubit_list) + layeredCircuit.append(pauli_circ, qubit_list, copy=False) return layeredCircuit diff --git a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py index 07139b223b1d..672d0eb9e8ef 100644 --- a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py +++ b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py @@ -137,7 +137,7 @@ def generate_basic_approximations( basis = [] for gate in basis_gates: if isinstance(gate, str): - if gate not in _1q_gates.keys(): + if gate not in _1q_gates: raise ValueError(f"Invalid gate identifier: {gate}") basis.append(gate) else: # gate is a qiskit.circuit.Gate diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index 62ad50582d40..e1db47beaeff 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -180,7 +180,7 @@ def _remove_inverse_follows_gate(sequence): while index < len(sequence.gates) - 1: curr_gate = sequence.gates[index] next_gate = sequence.gates[index + 1] - if curr_gate.name in _1q_inverses.keys(): + if curr_gate.name in _1q_inverses: remove = _1q_inverses[curr_gate.name] == next_gate.name else: remove = curr_gate.inverse() == next_gate diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 54599e00b9ae..400d98304951 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -154,8 +154,10 @@ HLSConfig SolovayKitaev -Post Layout (Post transpile qubit selection) -============================================ +Post Layout +=========== + +These are post qubit selection. .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 074c6d341baa..04bf852e55b5 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -97,8 +97,8 @@ class BasisTranslator(TransformationPass): When this error occurs it typically means that either the target basis is not universal or there are additional equivalence rules needed in the - :clas:~.EquivalenceLibrary` instance being used by the - :class:~.BasisTranslator` pass. You can refer to + :class:`~.EquivalenceLibrary` instance being used by the + :class:`~.BasisTranslator` pass. You can refer to :ref:`custom_basis_gates` for details on adding custom equivalence rules. """ diff --git a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py index 51b39d7e961b..e0dd61ff6cf4 100644 --- a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py @@ -218,8 +218,8 @@ def collect_key(x): prev = bit self.gate_groups[self.find_set(prev)].append(nd) # need to turn all groups that still exist into their own blocks - for index in self.parent: - if self.parent[index] == index and len(self.gate_groups[index]) != 0: + for index, item in self.parent.items(): + if item == index and len(self.gate_groups[index]) != 0: block_list.append(self.gate_groups[index][:]) self.property_set["block_list"] = block_list diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index 402aa9146f0a..501400f70ced 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -160,8 +160,13 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if len(dag.qubits) != next(iter(dag.qregs.values())).size: raise TranspilerError("Circuit has qubits not contained in the qubit register.") - new_dag = dag.copy_empty_like() + # Fix output permutation -- copied from ElidePermutations + input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)} + self.property_set["original_layout"] = Layout(input_qubit_mapping) + if self.property_set["original_qubit_indices"] is None: + self.property_set["original_qubit_indices"] = input_qubit_mapping + new_dag = dag.copy_empty_like() current_layout = Layout.generate_trivial_layout(*dag.qregs.values()) # Used to keep track of nodes that do not decompose using swap strategies. @@ -183,6 +188,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: self._compose_non_swap_nodes(accumulator, current_layout, new_dag) + self.property_set["virtual_permutation_layout"] = current_layout + return new_dag def _compose_non_swap_nodes( diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py index 4b27749c6dad..b79a298ad595 100644 --- a/qiskit/transpiler/passes/routing/star_prerouting.py +++ b/qiskit/transpiler/passes/routing/star_prerouting.py @@ -329,13 +329,13 @@ def _apply_mapping(qargs, qubit_mapping, qubits): last_2q_gate = None int_digits = floor(log10(len(processing_order))) + 1 - processing_order_s = set(processing_order) + processing_order_index_map = { + node: f"a{str(index).zfill(int(int_digits))}" + for index, node in enumerate(processing_order) + } def tie_breaker_key(node): - if node in processing_order_s: - return "a" + str(processing_order.index(node)).zfill(int(int_digits)) - else: - return node.sort_key + return processing_order_index_map.get(node, node.sort_key) for node in dag.topological_op_nodes(key=tie_breaker_key): block_id = node_to_block_id.get(node, None) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index f2485bfee530..c57c6d76f9fb 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -698,13 +698,13 @@ def __init__(self): self.plugins_by_op = {} for plugin_name in self.plugins.names(): op_name, method_name = plugin_name.split(".") - if op_name not in self.plugins_by_op.keys(): + if op_name not in self.plugins_by_op: self.plugins_by_op[op_name] = [] self.plugins_by_op[op_name].append(method_name) def method_names(self, op_name): """Returns plugin methods for op_name.""" - if op_name in self.plugins_by_op.keys(): + if op_name in self.plugins_by_op: return self.plugins_by_op[op_name] else: return [] diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 359d42228c43..f7b5227c0266 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -816,7 +816,7 @@ def check_obj_params(parameters, obj): if qargs in self._gate_map[op_name]: return True if self._gate_map[op_name] is None or None in self._gate_map[op_name]: - return self._gate_name_map[op_name].num_qubits == len(qargs) and all( + return obj.num_qubits == len(qargs) and all( x < self.num_qubits for x in qargs ) return False @@ -942,7 +942,9 @@ def instructions(self): is globally defined. """ return [ - (self._gate_name_map[op], qarg) for op in self._gate_map for qarg in self._gate_map[op] + (self._gate_name_map[op], qarg) + for op, qargs in self._gate_map.items() + for qarg in qargs ] def instruction_properties(self, index): @@ -981,7 +983,7 @@ def instruction_properties(self, index): InstructionProperties: The instruction properties for the specified instruction tuple """ instruction_properties = [ - inst_props for op in self._gate_map for _, inst_props in self._gate_map[op].items() + inst_props for qargs in self._gate_map.values() for inst_props in qargs.values() ] return instruction_properties[index] diff --git a/qiskit/utils/__init__.py b/qiskit/utils/__init__.py index f5256f6f11ec..30935437ebf2 100644 --- a/qiskit/utils/__init__.py +++ b/qiskit/utils/__init__.py @@ -44,7 +44,7 @@ .. autofunction:: local_hardware_info .. autofunction:: is_main_process -A helper function for calling a custom function with python +A helper function for calling a custom function with Python :class:`~concurrent.futures.ProcessPoolExecutor`. Tasks can be executed in parallel using this function. .. autofunction:: parallel_map @@ -70,7 +70,7 @@ from . import optionals -from .parallel import parallel_map +from .parallel import parallel_map, should_run_in_parallel __all__ = [ "LazyDependencyManager", @@ -85,4 +85,5 @@ "is_main_process", "apply_prefix", "parallel_map", + "should_run_in_parallel", ] diff --git a/qiskit/utils/parallel.py b/qiskit/utils/parallel.py index d46036a478f9..f87eeb815967 100644 --- a/qiskit/utils/parallel.py +++ b/qiskit/utils/parallel.py @@ -48,6 +48,8 @@ from the multiprocessing library. """ +from __future__ import annotations + import os from concurrent.futures import ProcessPoolExecutor import sys @@ -101,6 +103,21 @@ def _task_wrapper(param): return task(value, *task_args, **task_kwargs) +def should_run_in_parallel(num_processes: int | None = None) -> bool: + """Return whether the current parallelisation configuration suggests that we should run things + like :func:`parallel_map` in parallel (``True``) or degrade to serial (``False``). + + Args: + num_processes: the number of processes requested for use (if given). + """ + num_processes = CPU_COUNT if num_processes is None else num_processes + return ( + num_processes > 1 + and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE" + and CONFIG.get("parallel_enabled", PARALLEL_DEFAULT) + ) + + def parallel_map( # pylint: disable=dangerous-default-value task, values, task_args=(), task_kwargs={}, num_processes=CPU_COUNT ): @@ -110,21 +127,20 @@ def parallel_map( # pylint: disable=dangerous-default-value result = [task(value, *task_args, **task_kwargs) for value in values] - On Windows this function defaults to a serial implementation to avoid the - overhead from spawning processes in Windows. + This will parallelise the results if the number of ``values`` is greater than one, and the + current system configuration permits parallelization. Args: task (func): Function that is to be called for each value in ``values``. - values (array_like): List or array of values for which the ``task`` - function is to be evaluated. + values (array_like): List or array of values for which the ``task`` function is to be + evaluated. task_args (list): Optional additional arguments to the ``task`` function. task_kwargs (dict): Optional additional keyword argument to the ``task`` function. num_processes (int): Number of processes to spawn. Returns: - result: The result list contains the value of - ``task(value, *task_args, **task_kwargs)`` for - each value in ``values``. + result: The result list contains the value of ``task(value, *task_args, **task_kwargs)`` for + each value in ``values``. Raises: QiskitError: If user interrupts via keyboard. @@ -147,12 +163,7 @@ def func(_): if len(values) == 1: return [task(values[0], *task_args, **task_kwargs)] - # Run in parallel if not Win and not in parallel already - if ( - num_processes > 1 - and os.getenv("QISKIT_IN_PARALLEL") == "FALSE" - and CONFIG.get("parallel_enabled", PARALLEL_DEFAULT) - ): + if should_run_in_parallel(num_processes): os.environ["QISKIT_IN_PARALLEL"] = "TRUE" try: results = [] @@ -173,8 +184,6 @@ def func(_): os.environ["QISKIT_IN_PARALLEL"] = "FALSE" return results - # Cannot do parallel on Windows , if another parallel_map is running in parallel, - # or len(values) == 1. results = [] for _, value in enumerate(values): result = task(value, *task_args, **task_kwargs) diff --git a/qiskit/visualization/bloch.py b/qiskit/visualization/bloch.py index bae0633a811c..513d2ddff85e 100644 --- a/qiskit/visualization/bloch.py +++ b/qiskit/visualization/bloch.py @@ -290,7 +290,7 @@ def set_label_convention(self, convention): self.zlabel = ["$\\circlearrowleft$", "$\\circlearrowright$"] self.xlabel = ["$\\leftrightarrow$", "$\\updownarrow$"] else: - raise Exception("No such convention.") + raise ValueError("No such convention.") def __str__(self): string = "" @@ -396,7 +396,7 @@ def add_annotation(self, state_or_vector, text, **kwargs): if isinstance(state_or_vector, (list, np.ndarray, tuple)) and len(state_or_vector) == 3: vec = state_or_vector else: - raise Exception("Position needs to be specified by a qubit " + "state or a 3D vector.") + raise TypeError("Position needs to be specified by a qubit state or a 3D vector.") self.annotations.append({"position": vec, "text": text, "opts": kwargs}) def make_sphere(self): diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py index ad4b8e070e13..9341126bcd1a 100644 --- a/qiskit/visualization/circuit/latex.py +++ b/qiskit/visualization/circuit/latex.py @@ -213,17 +213,22 @@ def _initialize_latex_array(self): self._latex.append([" "] * (self._img_depth + 1)) # display the bit/register labels - for wire in self._wire_map: + for wire, index in self._wire_map.items(): if isinstance(wire, ClassicalRegister): register = wire - index = self._wire_map[wire] + wire_label = get_wire_label( + "latex", register, index, layout=self._layout, cregbundle=self._cregbundle + ) else: register, bit_index, reg_index = get_bit_reg_index(self._circuit, wire) - index = bit_index if register is None else reg_index + wire_label = get_wire_label( + "latex", + register, + bit_index if register is None else reg_index, + layout=self._layout, + cregbundle=self._cregbundle, + ) - wire_label = get_wire_label( - "latex", register, index, layout=self._layout, cregbundle=self._cregbundle - ) wire_label += " : " if self._initial_state: wire_label += "\\ket{{0}}" if isinstance(wire, Qubit) else "0" @@ -234,7 +239,7 @@ def _initialize_latex_array(self): self._latex[pos][1] = "\\lstick{/_{_{" + str(register.size) + "}}} \\cw" wire_label = f"\\mathrm{{{wire_label}}}" else: - pos = self._wire_map[wire] + pos = index self._latex[pos][0] = "\\nghost{" + wire_label + " & " + "\\lstick{" + wire_label def _get_image_depth(self): @@ -620,11 +625,11 @@ def _add_condition(self, op, wire_list, col): # First sort the val_bits in the order of the register bits in the circuit cond_wires = [] cond_bits = [] - for wire in self._wire_map: + for wire, index in self._wire_map.items(): reg, _, reg_index = get_bit_reg_index(self._circuit, wire) if reg == cond_reg: cond_bits.append(reg_index) - cond_wires.append(self._wire_map[wire]) + cond_wires.append(index) gap = cond_wires[0] - max(wire_list) prev_wire = cond_wires[0] diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index c547846acc5b..b4252065006c 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -893,7 +893,7 @@ def _draw_regs_wires(self, num_folds, xmax, max_x_index, qubits_dict, clbits_dic this_clbit_dict = {} for clbit in clbits_dict.values(): y = clbit["y"] - fold_num * (glob_data["n_lines"] + 1) - if y not in this_clbit_dict.keys(): + if y not in this_clbit_dict: this_clbit_dict[y] = { "val": 1, "wire_label": clbit["wire_label"], diff --git a/qiskit/visualization/circuit/qcstyle.py b/qiskit/visualization/circuit/qcstyle.py index a8432ca86a9e..67ae9faaf24b 100644 --- a/qiskit/visualization/circuit/qcstyle.py +++ b/qiskit/visualization/circuit/qcstyle.py @@ -72,7 +72,7 @@ class StyleDict(dict): def __setitem__(self, key: Any, value: Any) -> None: # allow using field abbreviations - if key in self.ABBREVIATIONS.keys(): + if key in self.ABBREVIATIONS: key = self.ABBREVIATIONS[key] if key not in self.VALID_FIELDS: @@ -85,7 +85,7 @@ def __setitem__(self, key: Any, value: Any) -> None: def __getitem__(self, key: Any) -> Any: # allow using field abbreviations - if key in self.ABBREVIATIONS.keys(): + if key in self.ABBREVIATIONS: key = self.ABBREVIATIONS[key] return super().__getitem__(key) diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py index abefe5511775..c2846da0b758 100644 --- a/qiskit/visualization/circuit/text.py +++ b/qiskit/visualization/circuit/text.py @@ -894,10 +894,9 @@ def wire_names(self, with_initial_state=False): self._wire_map = get_wire_map(self._circuit, (self.qubits + self.clbits), self.cregbundle) wire_labels = [] - for wire in self._wire_map: + for wire, index in self._wire_map.items(): if isinstance(wire, ClassicalRegister): register = wire - index = self._wire_map[wire] else: register, bit_index, reg_index = get_bit_reg_index(self._circuit, wire) index = bit_index if register is None else reg_index diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py index d8ffb6e10388..b950c84c902a 100644 --- a/qiskit/visualization/gate_map.py +++ b/qiskit/visualization/gate_map.py @@ -1039,7 +1039,9 @@ def plot_coupling_map( graph = CouplingMap(coupling_map).graph if not plot_directed: + line_color_map = dict(zip(graph.edge_list(), line_color)) graph = graph.to_undirected(multigraph=False) + line_color = [line_color_map[edge] for edge in graph.edge_list()] for node in graph.node_indices(): graph[node] = node @@ -1122,7 +1124,13 @@ def plot_circuit_layout(circuit, backend, view="virtual", qubit_coordinates=None Args: circuit (QuantumCircuit): Input quantum circuit. backend (Backend): Target backend. - view (str): Layout view: either 'virtual' or 'physical'. + view (str): How to label qubits in the layout. Options: + + - ``"virtual"``: Label each qubit with the index of the virtual qubit that + mapped to it. + - ``"physical"``: Label each qubit with the index of the physical qubit that it + corresponds to on the device. + qubit_coordinates (Sequence): An optional sequence input (list or array being the most common) of 2d coordinates for each qubit. The length of the sequence must match the number of qubits on the backend. The sequence diff --git a/qiskit/visualization/transition_visualization.py b/qiskit/visualization/transition_visualization.py index f322be64a4f5..a2ff74799999 100644 --- a/qiskit/visualization/transition_visualization.py +++ b/qiskit/visualization/transition_visualization.py @@ -72,10 +72,10 @@ def __mul__(self, b): return self._multiply_with_quaternion(b) elif isinstance(b, (list, tuple, np.ndarray)): if len(b) != 3: - raise Exception(f"Input vector has invalid length {len(b)}") + raise ValueError(f"Input vector has invalid length {len(b)}") return self._multiply_with_vector(b) else: - raise Exception(f"Multiplication with unknown type {type(b)}") + return NotImplemented def _multiply_with_quaternion(self, q_2): """Multiplication of quaternion with quaternion""" diff --git a/releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml b/releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml index 9de8affebe49..6718cd66f1f6 100644 --- a/releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml +++ b/releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml @@ -54,3 +54,16 @@ features_circuits: Variables can be used wherever classical expressions (see :mod:`qiskit.circuit.classical.expr`) are valid. Currently this is the target expressions of control-flow operations, though we plan to expand this to gate parameters in the future, as the type and expression system are expanded. + + See :ref:`circuit-repr-real-time-classical` for more discussion of these variables, and the + associated data model. + + These are supported throughout the transpiler, through QPY serialization (:mod:`qiskit.qpy`), + OpenQASM 3 export (:mod:`qiskit.qasm3`), and have initial support through the circuit visualizers + (see :meth:`.QuantumCircuit.draw`). + + .. note:: + + The new classical variables and storage will take some time to become supported on hardware + and simulator backends. They are not supported in the primitives interfaces + (:mod:`qiskit.primitives`), but will likely inform those interfaces as they evolve. diff --git a/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml b/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml new file mode 100644 index 000000000000..7f2e20db6522 --- /dev/null +++ b/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml @@ -0,0 +1,14 @@ +--- +features_circuits: + - | + Added a new function to ``qiskit.circuit.random`` that allows to generate a pseudo-random + Clifford circuit with gates from the standard library: :func:`.random_clifford_circuit`. + Example usage: + + .. plot:: + :include-source: + + from qiskit.circuit.random import random_clifford_circuit + + circ = random_clifford_circuit(num_qubits=2, num_gates=6) + circ.draw(output='mpl') diff --git a/releasenotes/notes/fix-apply-layout-duplicate-negative-indices-cf5517921fe52706.yaml b/releasenotes/notes/fix-apply-layout-duplicate-negative-indices-cf5517921fe52706.yaml new file mode 100644 index 000000000000..9fbe0ffd9c79 --- /dev/null +++ b/releasenotes/notes/fix-apply-layout-duplicate-negative-indices-cf5517921fe52706.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed :meth:`.SparsePauliOp.apply_layout` and :meth:`.Pauli.apply_layout` + to raise :exc:`.QiskitError` if duplicate indices or negative indices are provided + as part of a layout. diff --git a/releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml b/releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml new file mode 100644 index 000000000000..4eeaa9aa3d7a --- /dev/null +++ b/releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix a bug in :class:`~.library.Isometry` due to an unnecessary assertion, + that led to an error in :meth:`.UnitaryGate.control` + when :class:`~.library.UnitaryGate` had more that two qubits. diff --git a/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml new file mode 100644 index 000000000000..117230aee53c --- /dev/null +++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml @@ -0,0 +1,10 @@ +fixes: + - | + Fixed :meth:`.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators. + For example, if you previously created a 0 qubit and applied a layout like:: + + op = SparsePauliOp("") + op.apply_layout(None, 3) + + this would have previously raised an error. Now this will correctly return an operator of the form: + ``SparsePauliOp(['III'], coeffs=[1.+0.j])`` diff --git a/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml b/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml new file mode 100644 index 000000000000..834d7986ab85 --- /dev/null +++ b/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an oversight in the :class:`.Commuting2qGateRouter` transpiler pass where the qreg permutations + were not added to the pass property set, so they would have to be tracked manually by the user. Now it's + possible to access the permutation through the output circuit's ``layout`` property and plug the pass + into any transpilation pipeline without loss of information. diff --git a/releasenotes/notes/fix-symbolic-unit-scaling-c3eb4d9be674dfd6.yaml b/releasenotes/notes/fix-symbolic-unit-scaling-c3eb4d9be674dfd6.yaml new file mode 100644 index 000000000000..5ca00904a9ae --- /dev/null +++ b/releasenotes/notes/fix-symbolic-unit-scaling-c3eb4d9be674dfd6.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed a floating-point imprecision when scaling certain pulse units + between seconds and nanoseconds. If the pulse was symbolically defined, + an unnecessary floating-point error could be introduced by the scaling + for certain builds of ``symengine``, which could manifest in unexpected + results once the symbols were fully bound. See `#12392 `__. diff --git a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml new file mode 100644 index 000000000000..73da8e6b7ad3 --- /dev/null +++ b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + The :class:'.StabilizerState' class now has a new method + :meth:'~.StabilizerState.probabilities_dict_from_bitstring' allowing the + user to pass single bitstring to measure an outcome for. Previouslly the + :meth:'~.StabilizerState.probabilities_dict' would be utilized and would + at worst case calculate (2^n) number of probabilbity calculations (depending + on the state), even if a user wanted a single result. With this new method + the user can calculate just the single outcome bitstring value a user passes + to measure the probability for. As the number of qubits increases, the more + prevelant the performance enhancement may be (depending on the state) as only + 1 bitstring result is measured. diff --git a/releasenotes/notes/parallel-check-8186a8f074774a1f.yaml b/releasenotes/notes/parallel-check-8186a8f074774a1f.yaml new file mode 100644 index 000000000000..d3266b2aa5f2 --- /dev/null +++ b/releasenotes/notes/parallel-check-8186a8f074774a1f.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + :meth:`.PassManager.run` will no longer waste time serializing itself when given multiple inputs + if it is only going to work in serial. diff --git a/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml b/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml new file mode 100644 index 000000000000..72f2c95962a5 --- /dev/null +++ b/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a bug in :func:`plot_coupling_map` that caused the edges of the coupling map to be colored incorrectly. + See https://github.com/Qiskit/qiskit/pull/12369 for details. diff --git a/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml b/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml new file mode 100644 index 000000000000..2fb1b4dcc5a1 --- /dev/null +++ b/releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + The OpenQASM 2.0 parser (:func:`.qasm2.load` and :func:`.qasm2.loads`) can now evaluate + gate-angle expressions including integer operands that would overflow the system-size integer. + These will be evaluated in a double-precision floating-point context, just like the rest of the + expression always has been. Beware: an arbitrarily large integer will not necessarily be + exactly representable in double-precision floating-point, so there is a chance that however the + circuit was generated, it had already lost all numerical precision modulo :math:`2\pi`. diff --git a/releasenotes/notes/replace-initialization-algorithm-by-isometry-41f9ffa58f72ece5.yaml b/releasenotes/notes/replace-initialization-algorithm-by-isometry-41f9ffa58f72ece5.yaml new file mode 100644 index 000000000000..5bf8e7a80b48 --- /dev/null +++ b/releasenotes/notes/replace-initialization-algorithm-by-isometry-41f9ffa58f72ece5.yaml @@ -0,0 +1,7 @@ +--- +features_circuits: + - | + Replacing the internal synthesis algorithm of :class:`~.library.StatePreparation` + and :class:`~.library.Initialize` of Shende et al. by the algorithm given in + :class:`~.library.Isometry` of Iten et al. + The new algorithm reduces the number of CX gates and the circuit depth by a factor of 2. diff --git a/releasenotes/notes/storage-var-a00a33fcf9a71f3f.yaml b/releasenotes/notes/storage-var-a00a33fcf9a71f3f.yaml new file mode 100644 index 000000000000..b3b18be2fc1a --- /dev/null +++ b/releasenotes/notes/storage-var-a00a33fcf9a71f3f.yaml @@ -0,0 +1,122 @@ +--- +features_circuits: + - | + :class:`.QuantumCircuit` has several new methods to work with and inspect manual :class:`.Var` + variables. + + See :ref:`circuit-real-time-methods` for more in-depth discussion on all of these. + + The new methods are: + + * :meth:`~.QuantumCircuit.add_var` + * :meth:`~.QuantumCircuit.add_input` + * :meth:`~.QuantumCircuit.add_capture` + * :meth:`~.QuantumCircuit.add_uninitialized_var` + * :meth:`~.QuantumCircuit.get_var` + * :meth:`~.QuantumCircuit.has_var` + * :meth:`~.QuantumCircuit.iter_vars` + * :meth:`~.QuantumCircuit.iter_declared_vars` + * :meth:`~.QuantumCircuit.iter_captured_vars` + * :meth:`~.QuantumCircuit.iter_input_vars` + * :meth:`~.QuantumCircuit.store` + + In addition, there are several new dynamic attributes on :class:`.QuantumCircuit` surrounding + these variables: + + * :attr:`~.QuantumCircuit.num_vars` + * :attr:`~.QuantumCircuit.num_input_vars` + * :attr:`~.QuantumCircuit.num_captured_vars` + * :attr:`~.QuantumCircuit.num_declared_vars` + - | + :class:`.ControlFlowOp` and its subclasses now have a :meth:`~.ControlFlowOp.iter_captured_vars` + method, which will return an iterator over the unique variables captured in any of its immediate + blocks. + - | + :class:`.DAGCircuit` has several new methods to work with and inspect manual :class:`.Var` + variables. These are largely equivalent to their :class:`.QuantumCircuit` counterparts, except + that the :class:`.DAGCircuit` ones are optimized for programmatic access with already defined + objects, while the :class:`.QuantumCircuit` methods are more focussed on interactive human use. + + The new methods are: + + * :meth:`~.DAGCircuit.add_input_var` + * :meth:`~.DAGCircuit.add_captured_var` + * :meth:`~.DAGCircuit.add_declared_var` + * :meth:`~.DAGCircuit.has_var` + * :meth:`~.DAGCircuit.iter_vars` + * :meth:`~.DAGCircuit.iter_declared_vars` + * :meth:`~.DAGCircuit.iter_captured_vars` + * :meth:`~.DAGCircuit.iter_input_vars` + + There are also new public attributes: + + * :attr:`~.DAGCircuit.num_vars` + * :attr:`~.DAGCircuit.num_input_vars` + * :attr:`~.DAGCircuit.num_captured_vars` + * :attr:`~.DAGCircuit.num_declared_vars` + - | + :attr:`.DAGCircuit.wires` will now also contain any :class:`.Var` manual variables in the + circuit as well, as these are also classical data flow. + - | + A new method, :meth:`.Var.new`, is added to manually construct a real-time classical variable + that owns its memory. + - | + :meth:`.QuantumCircuit.compose` has two need keyword arguments, ``var_remap`` and ``inline_captures`` + to better support real-time classical variables. + + ``var_remap`` can be used to rewrite :class:`.Var` nodes in the circuit argument as its + instructions are inlined onto the base circuit. This can be used to avoid naming conflicts. + + ``inline_captures`` can be set to ``True`` (defaults to ``False``) to link all :class:`.Var` + nodes tracked as "captures" in the argument circuit with the same :class:`.Var` nodes in the + base circuit, without attempting to redeclare the variables. This can be used, in combination + with :meth:`.QuantumCircuit.copy_empty_like`'s ``vars_mode="captures"`` handling, to build up + a circuit layer by layer, containing variables. + - | + :meth:`.DAGCircuit.compose` has a new keyword argument, ``inline_captures``, which can be set to + ``True`` to inline "captured" :class:`.Var` nodes on the argument circuit onto the base circuit + without redeclaring them. In conjunction with the ``vars_mode="captures"`` option to several + :class:`.DAGCircuit` methods, this can be used to combine DAGs that operate on the same variables. + - | + :meth:`.QuantumCircuit.copy_empty_like` and :meth:`.DAGCircuit.copy_empty_like` have a new + keyword argument, ``vars_mode`` which controls how any memory-owning :class:`.Var` nodes are + tracked in the output. By default (``"alike"``), the variables are declared in the same + input/captured/local mode as the source. This can be set to ``"captures"`` to convert all + variables to captures (useful with :meth:`~.QuantumCircuit.compose`) or ``"drop"`` to remove + them. + - | + A new ``vars_mode`` keyword argument has been added to the :class:`.DAGCircuit` methods: + + * :meth:`~.DAGCircuit.separable_circuits` + * :meth:`~.DAGCircuit.layers` + * :meth:`~.DAGCircuit.serial_layers` + + which has the same meaning as it does for :meth:`~.DAGCircuit.copy_empty_like`. +features_qasm: + - | + The OpenQASM 3 exporter supports manual-storage :class:`.Var` nodes on circuits. +features_qpy: + - | + QPY (:mod:`qiskit.qpy`) format version 12 has been added, which includes support for memory-owning + :class:`.Var` variables. See :ref:`qpy_version_12` for more detail on the format changes. +features_visualization: + - | + The text and `Matplotlib `__ circuit drawers (:meth:`.QuantumCircuit.draw`) + have minimal support for displaying expressions involving manual real-time variables. The + :class:`.Store` operation and the variable initializations are not yet supported; for large-scale + dynamic circuits, we recommend using the OpenQASM 3 export capabilities (:func:`.qasm3.dumps`) to + get a textual representation of a circuit. +upgrade_qpy: + - | + The value of :attr:`qiskit.qpy.QPY_VERSION` is now 12. :attr:`.QPY_COMPATIBILITY_VERSION` is + unchanged at 10. +upgrade_providers: + - | + Implementations of :class:`.BackendV2` (and :class:`.BackendV1`) may desire to update their + :meth:`~.BackendV2.run` methods to eagerly reject inputs containing typed + classical variables (see :mod:`qiskit.circuit.classical`) and the :class:`.Store` instruction, + if they do not have support for them. The new :class:`.Store` instruction is treated by the + transpiler as an always-available "directive" (like :class:`.Barrier`); if your backends do not + support this won't be caught by the :mod:`~qiskit.transpiler`. + + See :ref:`providers-guide-real-time-variables` for more information. diff --git a/test/benchmarks/statepreparation.py b/test/benchmarks/statepreparation.py new file mode 100644 index 000000000000..67dc1178fc24 --- /dev/null +++ b/test/benchmarks/statepreparation.py @@ -0,0 +1,66 @@ +# 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. + +# pylint: disable=missing-docstring,invalid-name,no-member +# pylint: disable=attribute-defined-outside-init +# pylint: disable=unused-argument + +import numpy as np +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.compiler import transpile +from qiskit.circuit.library.data_preparation import StatePreparation + + +class StatePreparationTranspileBench: + params = [4, 5, 6, 7, 8] + param_names = ["number of qubits in state"] + + def setup(self, n): + q = QuantumRegister(n) + qc = QuantumCircuit(q) + state = np.random.rand(2**n) + np.random.rand(2**n) * 1j + state = state / np.linalg.norm(state) + state_gate = StatePreparation(state) + qc.append(state_gate, q) + + self.circuit = qc + + def track_cnot_counts_after_mapping_to_ibmq_16_melbourne(self, *unused): + coupling = [ + [1, 0], + [1, 2], + [2, 3], + [4, 3], + [4, 10], + [5, 4], + [5, 6], + [5, 9], + [6, 8], + [7, 8], + [9, 8], + [9, 10], + [11, 3], + [11, 10], + [11, 12], + [12, 2], + [13, 1], + [13, 12], + ] + circuit = transpile( + self.circuit, + basis_gates=["u1", "u3", "u2", "cx"], + coupling_map=coupling, + seed_transpiler=0, + ) + counts = circuit.count_ops() + cnot_count = counts.get("cx", 0) + return cnot_count diff --git a/test/benchmarks/utils.py b/test/benchmarks/utils.py index af8f3318074a..d932e8d6a0c5 100644 --- a/test/benchmarks/utils.py +++ b/test/benchmarks/utils.py @@ -71,7 +71,7 @@ def random_circuit( Exception: when invalid options given """ if max_operands < 1 or max_operands > 3: - raise Exception("max_operands must be between 1 and 3") + raise ValueError("max_operands must be between 1 and 3") one_q_ops = [ IGate, diff --git a/test/python/circuit/library/test_overlap.py b/test/python/circuit/library/test_overlap.py index a603f28037b1..1a95e3ba9155 100644 --- a/test/python/circuit/library/test_overlap.py +++ b/test/python/circuit/library/test_overlap.py @@ -131,6 +131,21 @@ def test_mismatching_qubits(self): with self.assertRaises(CircuitError): _ = UnitaryOverlap(unitary1, unitary2) + def test_insert_barrier(self): + """Test inserting barrier between circuits""" + unitary1 = EfficientSU2(1, reps=1) + unitary2 = EfficientSU2(1, reps=1) + overlap = UnitaryOverlap(unitary1, unitary2, insert_barrier=True) + self.assertEqual(overlap.count_ops()["barrier"], 1) + self.assertEqual( + str(overlap.draw(fold=-1, output="text")).strip(), + """ + ┌───────────────────────────────────────┐ ░ ┌──────────────────────────────────────────┐ +q: ┤ EfficientSU2(p1[0],p1[1],p1[2],p1[3]) ├─░─┤ EfficientSU2_dg(p2[0],p2[1],p2[2],p2[3]) ├ + └───────────────────────────────────────┘ ░ └──────────────────────────────────────────┘ +""".strip(), + ) + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 0e481c12b33b..db6280b88230 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -820,13 +820,16 @@ def test_expr_condition_is_mapped(self): b_src = ClassicalRegister(2, "b_src") c_src = ClassicalRegister(name="c_src", bits=list(a_src) + list(b_src)) source = QuantumCircuit(QuantumRegister(1), a_src, b_src, c_src) + target_var = source.add_input("target_var", types.Uint(2)) test_1 = lambda: expr.lift(a_src[0]) test_2 = lambda: expr.logic_not(b_src[1]) test_3 = lambda: expr.logic_and(expr.bit_and(b_src, 2), expr.less(c_src, 7)) + test_4 = lambda: expr.bit_xor(expr.index(target_var, 0), expr.index(target_var, 1)) source.if_test(test_1(), inner.copy(), [0], []) source.if_else(test_2(), inner.copy(), inner.copy(), [0], []) source.while_loop(test_3(), inner.copy(), [0], []) + source.if_test(test_4(), inner.copy(), [0], []) a_dest = ClassicalRegister(2, "a_dest") b_dest = ClassicalRegister(2, "b_dest") @@ -840,12 +843,19 @@ def test_expr_condition_is_mapped(self): self.assertEqual(len(dest.cregs), 3) mapped_reg = dest.cregs[-1] - expected = QuantumCircuit(dest.qregs[0], a_dest, b_dest, mapped_reg) + expected = QuantumCircuit(dest.qregs[0], a_dest, b_dest, mapped_reg, inputs=[target_var]) expected.if_test(expr.lift(a_dest[0]), inner.copy(), [0], []) expected.if_else(expr.logic_not(b_dest[1]), inner.copy(), inner.copy(), [0], []) expected.while_loop( expr.logic_and(expr.bit_and(b_dest, 2), expr.less(mapped_reg, 7)), inner.copy(), [0], [] ) + # `Var` nodes aren't remapped, but this should be passed through fine. + expected.if_test( + expr.bit_xor(expr.index(target_var, 0), expr.index(target_var, 1)), + inner.copy(), + [0], + [], + ) self.assertEqual(dest, expected) def test_expr_target_is_mapped(self): diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index f0d6dd3a8f7e..ced7229415ea 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -852,10 +852,9 @@ def test_controlled_unitary(self, num_ctrl_qubits): self.assertTrue(is_unitary_matrix(base_mat)) self.assertTrue(matrix_equal(cop_mat, test_op.data)) - @data(1, 2, 3, 4, 5) - def test_controlled_random_unitary(self, num_ctrl_qubits): + @combine(num_ctrl_qubits=(1, 2, 3, 4, 5), num_target=(2, 3)) + def test_controlled_random_unitary(self, num_ctrl_qubits, num_target): """Test the matrix data of an Operator based on a random UnitaryGate.""" - num_target = 2 base_gate = random_unitary(2**num_target).to_instruction() base_mat = base_gate.to_matrix() cgate = base_gate.control(num_ctrl_qubits) diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py index 9f6c007b1d5b..b03818846c82 100644 --- a/test/python/primitives/test_backend_sampler_v2.py +++ b/test/python/primitives/test_backend_sampler_v2.py @@ -640,9 +640,9 @@ def test_circuit_with_aliased_cregs(self, backend): self.assertEqual(len(result), 1) data = result[0].data self.assertEqual(len(data), 3) - for creg_name in target: + for creg_name, creg in target.items(): self.assertTrue(hasattr(data, creg_name)) - self._assert_allclose(getattr(data, creg_name), np.array(target[creg_name])) + self._assert_allclose(getattr(data, creg_name), np.array(creg)) @combine(backend=BACKENDS) def test_no_cregs(self, backend): diff --git a/test/python/primitives/test_statevector_sampler.py b/test/python/primitives/test_statevector_sampler.py index de17b2824075..c065871025d7 100644 --- a/test/python/primitives/test_statevector_sampler.py +++ b/test/python/primitives/test_statevector_sampler.py @@ -606,9 +606,9 @@ def test_circuit_with_aliased_cregs(self): self.assertEqual(len(result), 1) data = result[0].data self.assertEqual(len(data), 3) - for creg_name in target: + for creg_name, creg in target.items(): self.assertTrue(hasattr(data, creg_name)) - self._assert_allclose(getattr(data, creg_name), np.array(target[creg_name])) + self._assert_allclose(getattr(data, creg_name), np.array(creg)) def test_no_cregs(self): """Test that the sampler works when there are no classical register in the circuit.""" diff --git a/test/python/qasm2/test_expression.py b/test/python/qasm2/test_expression.py index 98aead7f3b43..2ef35abde9ec 100644 --- a/test/python/qasm2/test_expression.py +++ b/test/python/qasm2/test_expression.py @@ -123,6 +123,18 @@ def test_function_symbolic(self, function_str, function_py): actual = [float(x) for x in abstract_op.definition.data[0].operation.params] self.assertEqual(list(actual), expected) + def test_bigint(self): + """Test that an expression can be evaluated even if it contains an integer that will + overflow the integer handling.""" + bigint = 1 << 200 + # Sanity check that the number we're trying for is represented at full precision in floating + # point (which it should be - it's a power of two with fewer than 11 bits of exponent). + self.assertEqual(int(float(bigint)), bigint) + program = f"qreg q[1]; U({bigint}, -{bigint}, {bigint} * 2.0) q[0];" + parsed = qiskit.qasm2.loads(program) + parameters = list(parsed.data[0].operation.params) + self.assertEqual([bigint, -bigint, 2 * bigint], parameters) + class TestPrecedenceAssociativity(QiskitTestCase): def test_precedence(self): diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index f23c0155bc6d..3585efb9f646 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2023. +# (C) Copyright IBM 2017, 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 @@ -17,7 +17,9 @@ import numpy as np from ddt import ddt -from qiskit.circuit import Gate, QuantumCircuit, QuantumRegister +from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit.random import random_clifford_circuit + from qiskit.circuit.library import ( CPhaseGate, CRXGate, @@ -26,7 +28,6 @@ CXGate, CYGate, CZGate, - DCXGate, ECRGate, HGate, IGate, @@ -37,10 +38,7 @@ RYYGate, RZZGate, RZXGate, - SdgGate, SGate, - SXGate, - SXdgGate, SwapGate, XGate, XXMinusYYGate, @@ -57,98 +55,11 @@ from qiskit.quantum_info.operators import Clifford, Operator from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.quantum_info.operators.symplectic.clifford_circuits import _append_operation -from qiskit.synthesis.clifford import ( - synth_clifford_full, - synth_clifford_ag, - synth_clifford_bm, - synth_clifford_greedy, -) from qiskit.synthesis.linear import random_invertible_binary_matrix from test import QiskitTestCase # pylint: disable=wrong-import-order from test import combine # pylint: disable=wrong-import-order -class VGate(Gate): - """V Gate used in Clifford synthesis.""" - - def __init__(self): - """Create new V Gate.""" - super().__init__("v", 1, []) - - def _define(self): - """V Gate definition.""" - q = QuantumRegister(1, "q") - qc = QuantumCircuit(q) - qc.sdg(0) - qc.h(0) - self.definition = qc - - -class WGate(Gate): - """W Gate used in Clifford synthesis.""" - - def __init__(self): - """Create new W Gate.""" - super().__init__("w", 1, []) - - def _define(self): - """W Gate definition.""" - q = QuantumRegister(1, "q") - qc = QuantumCircuit(q) - qc.append(VGate(), [q[0]], []) - qc.append(VGate(), [q[0]], []) - self.definition = qc - - -def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): - """Generate a pseudo random Clifford circuit.""" - - qubits_1_gates = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg", "v", "w"] - qubits_2_gates = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] - if gates == "all": - if num_qubits == 1: - gates = qubits_1_gates - else: - gates = qubits_1_gates + qubits_2_gates - - instructions = { - "i": (IGate(), 1), - "x": (XGate(), 1), - "y": (YGate(), 1), - "z": (ZGate(), 1), - "h": (HGate(), 1), - "s": (SGate(), 1), - "sdg": (SdgGate(), 1), - "sx": (SXGate(), 1), - "sxdg": (SXdgGate(), 1), - "v": (VGate(), 1), - "w": (WGate(), 1), - "cx": (CXGate(), 2), - "cy": (CYGate(), 2), - "cz": (CZGate(), 2), - "swap": (SwapGate(), 2), - "iswap": (iSwapGate(), 2), - "ecr": (ECRGate(), 2), - "dcx": (DCXGate(), 2), - } - - if isinstance(seed, np.random.Generator): - rng = seed - else: - rng = np.random.default_rng(seed) - - samples = rng.choice(gates, num_gates) - - circ = QuantumCircuit(num_qubits) - - for name in samples: - gate, nqargs = instructions[name] - qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() - circ.append(gate, qargs) - - return circ - - @ddt class TestCliffordGates(QiskitTestCase): """Tests for clifford append gate functions.""" @@ -588,92 +499,6 @@ def test_from_circuit_with_all_types(self): self.assertEqual(combined_clifford, expected_clifford) -@ddt -class TestCliffordSynthesis(QiskitTestCase): - """Test Clifford synthesis methods.""" - - @staticmethod - def _cliffords_1q(): - clifford_dicts = [ - {"stabilizer": ["+Z"], "destabilizer": ["-X"]}, - {"stabilizer": ["-Z"], "destabilizer": ["+X"]}, - {"stabilizer": ["-Z"], "destabilizer": ["-X"]}, - {"stabilizer": ["+Z"], "destabilizer": ["+Y"]}, - {"stabilizer": ["+Z"], "destabilizer": ["-Y"]}, - {"stabilizer": ["-Z"], "destabilizer": ["+Y"]}, - {"stabilizer": ["-Z"], "destabilizer": ["-Y"]}, - {"stabilizer": ["+X"], "destabilizer": ["+Z"]}, - {"stabilizer": ["+X"], "destabilizer": ["-Z"]}, - {"stabilizer": ["-X"], "destabilizer": ["+Z"]}, - {"stabilizer": ["-X"], "destabilizer": ["-Z"]}, - {"stabilizer": ["+X"], "destabilizer": ["+Y"]}, - {"stabilizer": ["+X"], "destabilizer": ["-Y"]}, - {"stabilizer": ["-X"], "destabilizer": ["+Y"]}, - {"stabilizer": ["-X"], "destabilizer": ["-Y"]}, - {"stabilizer": ["+Y"], "destabilizer": ["+X"]}, - {"stabilizer": ["+Y"], "destabilizer": ["-X"]}, - {"stabilizer": ["-Y"], "destabilizer": ["+X"]}, - {"stabilizer": ["-Y"], "destabilizer": ["-X"]}, - {"stabilizer": ["+Y"], "destabilizer": ["+Z"]}, - {"stabilizer": ["+Y"], "destabilizer": ["-Z"]}, - {"stabilizer": ["-Y"], "destabilizer": ["+Z"]}, - {"stabilizer": ["-Y"], "destabilizer": ["-Z"]}, - ] - return [Clifford.from_dict(i) for i in clifford_dicts] - - def test_decompose_1q(self): - """Test synthesis for all 1-qubit Cliffords""" - for cliff in self._cliffords_1q(): - with self.subTest(msg=f"Test circuit {cliff}"): - target = cliff - value = Clifford(cliff.to_circuit()) - self.assertEqual(target, value) - - @combine(num_qubits=[2, 3]) - def test_synth_bm(self, num_qubits): - """Test B&M synthesis for set of {num_qubits}-qubit Cliffords""" - rng = np.random.default_rng(1234) - samples = 50 - for _ in range(samples): - circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) - target = Clifford(circ) - value = Clifford(synth_clifford_bm(target)) - self.assertEqual(value, target) - - @combine(num_qubits=[2, 3, 4, 5]) - def test_synth_ag(self, num_qubits): - """Test A&G synthesis for set of {num_qubits}-qubit Cliffords""" - rng = np.random.default_rng(1234) - samples = 50 - for _ in range(samples): - circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) - target = Clifford(circ) - value = Clifford(synth_clifford_ag(target)) - self.assertEqual(value, target) - - @combine(num_qubits=[1, 2, 3, 4, 5]) - def test_synth_greedy(self, num_qubits): - """Test greedy synthesis for set of {num_qubits}-qubit Cliffords""" - rng = np.random.default_rng(1234) - samples = 50 - for _ in range(samples): - circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) - target = Clifford(circ) - value = Clifford(synth_clifford_greedy(target)) - self.assertEqual(value, target) - - @combine(num_qubits=[1, 2, 3, 4, 5]) - def test_synth_full(self, num_qubits): - """Test synthesis for set of {num_qubits}-qubit Cliffords""" - rng = np.random.default_rng(1234) - samples = 50 - for _ in range(samples): - circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) - target = Clifford(circ) - value = Clifford(synth_clifford_full(target)) - self.assertEqual(value, target) - - @ddt class TestCliffordDecomposition(QiskitTestCase): """Test Clifford decompositions.""" @@ -683,11 +508,9 @@ class TestCliffordDecomposition(QiskitTestCase): ["h", "s"], ["h", "s", "i", "x", "y", "z"], ["h", "s", "sdg"], - ["h", "s", "v"], - ["h", "s", "w"], ["h", "sx", "sxdg"], ["s", "sx", "sxdg"], - ["h", "s", "sdg", "i", "x", "y", "z", "v", "w", "sx", "sxdg"], + ["h", "s", "sdg", "i", "x", "y", "z", "sx", "sxdg"], ] ) def test_to_operator_1qubit_gates(self, gates): diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index 875dd9237810..35acd46a4d02 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -14,42 +14,41 @@ """Tests for Pauli operator class.""" +import itertools as it import re import unittest -import itertools as it from functools import lru_cache +from test import QiskitTestCase, combine + import numpy as np -from ddt import ddt, data, unpack +from ddt import data, ddt, unpack from qiskit import QuantumCircuit from qiskit.circuit import Qubit -from qiskit.exceptions import QiskitError from qiskit.circuit.library import ( - IGate, - XGate, - YGate, - ZGate, - HGate, - SGate, - SdgGate, CXGate, - CZGate, CYGate, - SwapGate, + CZGate, ECRGate, EfficientSU2, + HGate, + IGate, + SdgGate, + SGate, + SwapGate, + XGate, + YGate, + ZGate, ) from qiskit.circuit.library.generalized_gates import PauliGate from qiskit.compiler.transpiler import transpile -from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.exceptions import QiskitError from qiskit.primitives import BackendEstimator +from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.quantum_info.operators import Operator, Pauli, SparsePauliOp from qiskit.quantum_info.random import random_clifford, random_pauli -from qiskit.quantum_info.operators import Pauli, Operator, SparsePauliOp from qiskit.utils import optionals -from test import QiskitTestCase # pylint: disable=wrong-import-order - - LABEL_REGEX = re.compile(r"(?P[+-]?1?[ij]?)(?P[IXYZ]*)") PHASE_MAP = {"": 0, "-i": 1, "-": 2, "i": 3} @@ -606,6 +605,25 @@ def test_apply_layout_null_layout_invalid_num_qubits(self): with self.assertRaises(QiskitError): op.apply_layout(layout=None, num_qubits=1) + def test_apply_layout_negative_indices(self): + """Test apply_layout with negative indices""" + op = Pauli("IZ") + with self.assertRaises(QiskitError): + op.apply_layout(layout=[-1, 0], num_qubits=3) + + def test_apply_layout_duplicate_indices(self): + """Test apply_layout with duplicate indices""" + op = Pauli("IZ") + with self.assertRaises(QiskitError): + op.apply_layout(layout=[0, 0], num_qubits=3) + + @combine(phase=["", "-i", "-", "i"], layout=[None, []]) + def test_apply_layout_zero_qubit(self, phase, layout): + """Test apply_layout with a zero-qubit operator""" + op = Pauli(phase) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(Pauli(phase + "IIIII"), res) + if __name__ == "__main__": unittest.main() diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py index 330fd53bc35d..e7a9b89b7318 100644 --- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py +++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py @@ -14,23 +14,22 @@ import itertools as it import unittest +from test import QiskitTestCase, combine + import numpy as np -import scipy.sparse import rustworkx as rx +import scipy.sparse from ddt import ddt - from qiskit import QiskitError -from qiskit.circuit import ParameterExpression, Parameter, ParameterVector -from qiskit.circuit.parametertable import ParameterView -from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp +from qiskit.circuit import Parameter, ParameterExpression, ParameterVector from qiskit.circuit.library import EfficientSU2 +from qiskit.circuit.parametertable import ParameterView +from qiskit.compiler.transpiler import transpile from qiskit.primitives import BackendEstimator from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.compiler.transpiler import transpile +from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp from qiskit.utils import optionals -from test import QiskitTestCase # pylint: disable=wrong-import-order -from test import combine # pylint: disable=wrong-import-order def pauli_mat(label): @@ -1179,6 +1178,34 @@ def test_apply_layout_null_layout_invalid_num_qubits(self): with self.assertRaises(QiskitError): op.apply_layout(layout=None, num_qubits=1) + def test_apply_layout_negative_indices(self): + """Test apply_layout with negative indices""" + op = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) + with self.assertRaises(QiskitError): + op.apply_layout(layout=[-1, 0], num_qubits=3) + + def test_apply_layout_duplicate_indices(self): + """Test apply_layout with duplicate indices""" + op = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) + with self.assertRaises(QiskitError): + op.apply_layout(layout=[0, 0], num_qubits=3) + + @combine(layout=[None, []]) + def test_apply_layout_zero_qubit(self, layout): + """Test apply_layout with a zero-qubit operator""" + with self.subTest("default"): + op = SparsePauliOp("") + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII"), res) + with self.subTest("coeff"): + op = SparsePauliOp("", 2) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII", 2), res) + with self.subTest("multiple ops"): + op = SparsePauliOp.from_list([("", 1), ("", 2)]) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp.from_list([("IIIII", 1), ("IIIII", 2)]), res) + if __name__ == "__main__": unittest.main() diff --git a/test/python/quantum_info/states/test_stabilizerstate.py b/test/python/quantum_info/states/test_stabilizerstate.py index 56fecafbe58b..4e1659ff6999 100644 --- a/test/python/quantum_info/states/test_stabilizerstate.py +++ b/test/python/quantum_info/states/test_stabilizerstate.py @@ -13,6 +13,7 @@ """Tests for Stabilizerstate quantum state class.""" +from itertools import product import unittest import logging from ddt import ddt, data, unpack @@ -32,6 +33,61 @@ logger = logging.getLogger(__name__) +class StabilizerStateTestingTools: + """Test tools for verifying test cases in StabilizerState""" + + @staticmethod + def _bitstring_product_dict(bitstring_length: int, skip_entries: dict = None) -> dict: + """Retrieves a dict of every possible product of '0', '1' for length bitstring_length + pass in a dict to use the keys as entries to skip adding to the dict + + Args: + bitstring_length (int): length of the bitstring product + skip_entries (dict[str, float], optional): dict entries to skip adding to the dict based + on existing keys in the dict passed in. Defaults to {}. + + Returns: + dict[str, float]: dict with entries, all set to 0 + """ + if skip_entries is None: + skip_entries = {} + return { + result: 0 + for result in ["".join(x) for x in product(["0", "1"], repeat=bitstring_length)] + if result not in skip_entries + } + + @staticmethod + def _verify_individual_bitstrings( + testcase: QiskitTestCase, + target_dict: dict, + stab: StabilizerState, + qargs: list = None, + decimals: int = None, + dict_almost_equal: bool = False, + ) -> None: + """Helper that iterates through the target_dict and checks all probabilities by + running the value through the probabilities_dict_from_bitstring method for + retrieving a single measurement + + Args: + target_dict (dict[str, float]): dict to check probabilities for + stab (StabilizerState): stabilizerstate object to run probabilities_dict_from_bitstring on + qargs (None or list): subsystems to return probabilities for, + if None return for all subsystems (Default: None). + decimals (None or int): the number of decimal places to round + values. If None no rounding is done (Default: None) + dict_almost_equal (bool): utilize assertDictAlmostEqual when true, assertDictEqual when false + """ + for outcome_bitstring in target_dict: + (testcase.assertDictAlmostEqual if (dict_almost_equal) else testcase.assertDictEqual)( + stab.probabilities_dict_from_bitstring( + outcome_bitstring=outcome_bitstring, qargs=qargs, decimals=decimals + ), + {outcome_bitstring: target_dict[outcome_bitstring]}, + ) + + @ddt class TestStabilizerState(QiskitTestCase): """Tests for StabilizerState class.""" @@ -315,6 +371,8 @@ def test_probabilities_dict_single_qubit(self): value = stab.probabilities_dict() target = {"0": 1} self.assertEqual(value, target) + target.update({"1": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab) probs = stab.probabilities() target = np.array([1, 0]) self.assertTrue(np.allclose(probs, target)) @@ -326,6 +384,8 @@ def test_probabilities_dict_single_qubit(self): value = stab.probabilities_dict() target = {"1": 1} self.assertEqual(value, target) + target.update({"0": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab) probs = stab.probabilities() target = np.array([0, 1]) self.assertTrue(np.allclose(probs, target)) @@ -338,6 +398,7 @@ def test_probabilities_dict_single_qubit(self): value = stab.probabilities_dict() target = {"0": 0.5, "1": 0.5} self.assertEqual(value, target) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab) probs = stab.probabilities() target = np.array([0.5, 0.5]) self.assertTrue(np.allclose(probs, target)) @@ -355,43 +416,56 @@ def test_probabilities_dict_two_qubits(self): value = stab.probabilities_dict() target = {"00": 0.5, "01": 0.5} self.assertEqual(value, target) + target.update({"10": 0.0, "11": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab) probs = stab.probabilities() target = np.array([0.5, 0.5, 0, 0]) self.assertTrue(np.allclose(probs, target)) + qargs: list = [0, 1] for _ in range(self.samples): with self.subTest(msg="P([0, 1])"): - value = stab.probabilities_dict([0, 1]) + value = stab.probabilities_dict(qargs) target = {"00": 0.5, "01": 0.5} self.assertEqual(value, target) - probs = stab.probabilities([0, 1]) + target.update({"10": 0.0, "11": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs) + probs = stab.probabilities(qargs) target = np.array([0.5, 0.5, 0, 0]) self.assertTrue(np.allclose(probs, target)) + qargs: list = [1, 0] for _ in range(self.samples): with self.subTest(msg="P([1, 0])"): - value = stab.probabilities_dict([1, 0]) + value = stab.probabilities_dict(qargs) target = {"00": 0.5, "10": 0.5} self.assertEqual(value, target) - probs = stab.probabilities([1, 0]) + target.update({"01": 0.0, "11": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs) + probs = stab.probabilities(qargs) target = np.array([0.5, 0, 0.5, 0]) self.assertTrue(np.allclose(probs, target)) + qargs: list = [0] for _ in range(self.samples): with self.subTest(msg="P[0]"): - value = stab.probabilities_dict([0]) + value = stab.probabilities_dict(qargs) target = {"0": 0.5, "1": 0.5} self.assertEqual(value, target) - probs = stab.probabilities([0]) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs) + probs = stab.probabilities(qargs) target = np.array([0.5, 0.5]) self.assertTrue(np.allclose(probs, target)) + qargs: list = [1] for _ in range(self.samples): with self.subTest(msg="P([1])"): - value = stab.probabilities_dict([1]) + value = stab.probabilities_dict(qargs) target = {"0": 1.0} self.assertEqual(value, target) - probs = stab.probabilities([1]) + target.update({"1": 0.0}) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs) + probs = stab.probabilities(qargs) target = np.array([1, 0]) self.assertTrue(np.allclose(probs, target)) @@ -405,9 +479,10 @@ def test_probabilities_dict_qubits(self): qc.h(2) stab = StabilizerState(qc) + decimals: int = 1 for _ in range(self.samples): with self.subTest(msg="P(None), decimals=1"): - value = stab.probabilities_dict(decimals=1) + value = stab.probabilities_dict(decimals=decimals) target = { "000": 0.1, "001": 0.1, @@ -419,13 +494,17 @@ def test_probabilities_dict_qubits(self): "111": 0.1, } self.assertEqual(value, target) - probs = stab.probabilities(decimals=1) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target, stab, decimals=decimals + ) + probs = stab.probabilities(decimals=decimals) target = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) self.assertTrue(np.allclose(probs, target)) + decimals: int = 2 for _ in range(self.samples): with self.subTest(msg="P(None), decimals=2"): - value = stab.probabilities_dict(decimals=2) + value = stab.probabilities_dict(decimals=decimals) target = { "000": 0.12, "001": 0.12, @@ -437,13 +516,17 @@ def test_probabilities_dict_qubits(self): "111": 0.12, } self.assertEqual(value, target) - probs = stab.probabilities(decimals=2) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target, stab, decimals=decimals + ) + probs = stab.probabilities(decimals=decimals) target = np.array([0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12]) self.assertTrue(np.allclose(probs, target)) + decimals: int = 3 for _ in range(self.samples): with self.subTest(msg="P(None), decimals=3"): - value = stab.probabilities_dict(decimals=3) + value = stab.probabilities_dict(decimals=decimals) target = { "000": 0.125, "001": 0.125, @@ -455,10 +538,72 @@ def test_probabilities_dict_qubits(self): "111": 0.125, } self.assertEqual(value, target) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target, stab, decimals=decimals + ) probs = stab.probabilities(decimals=3) target = np.array([0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125]) self.assertTrue(np.allclose(probs, target)) + @combine(num_qubits=[5, 6, 7, 8, 9]) + def test_probabilities_dict_from_bitstring(self, num_qubits): + """Test probabilities_dict_from_bitstring methods with medium number of qubits that are still + reasonable to calculate the full dict with probabilities_dict of all possible outcomes""" + + qc: QuantumCircuit = QuantumCircuit(num_qubits) + for qubit_num in range(0, num_qubits): + qc.h(qubit_num) + stab = StabilizerState(qc) + + expected_result: float = float(1 / (2**num_qubits)) + target_dict: dict = StabilizerStateTestingTools._bitstring_product_dict(num_qubits) + target_dict.update((k, expected_result) for k in target_dict) + + for _ in range(self.samples): + with self.subTest(msg="P(None)"): + value = stab.probabilities_dict() + self.assertDictEqual(value, target_dict) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target_dict, stab) + probs = stab.probabilities() + target = np.array(([expected_result] * (2**num_qubits))) + self.assertTrue(np.allclose(probs, target)) + + # H gate at qubit 0, Every gate after is an X gate + # will result in 2 outcomes with 0.5 + qc = QuantumCircuit(num_qubits) + qc.h(0) + for qubit_num in range(1, num_qubits): + qc.x(qubit_num) + stab = StabilizerState(qc) + + # Build the 2 expected outcome bitstrings for + # 0.5 probability based on h and x gates + target_1: str = "".join(["1" * (num_qubits - 1)] + ["0"]) + target_2: str = "".join(["1" * num_qubits]) + target: dict = {target_1: 0.5, target_2: 0.5} + target_all_bitstrings: dict = StabilizerStateTestingTools._bitstring_product_dict( + num_qubits, target + ) + target_all_bitstrings.update(target_all_bitstrings) + + # Numpy Array to verify stab.probabilities() + target_np_dict: dict = StabilizerStateTestingTools._bitstring_product_dict( + num_qubits, [target_1, target_2] + ) + target_np_dict.update(target) + target_np_array: np.ndarray = np.array(list(target_np_dict.values())) + + for _ in range(self.samples): + with self.subTest(msg="P(None)"): + stab = StabilizerState(qc) + value = stab.probabilities_dict() + self.assertEqual(value, target) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target_all_bitstrings, stab + ) + probs = stab.probabilities() + self.assertTrue(np.allclose(probs, target_np_array)) + def test_probabilities_dict_ghz(self): """Test probabilities and probabilities_dict method of a subsystem of qubits""" @@ -473,6 +618,8 @@ def test_probabilities_dict_ghz(self): value = stab.probabilities_dict() target = {"000": 0.5, "111": 0.5} self.assertEqual(value, target) + target.update(StabilizerStateTestingTools._bitstring_product_dict(num_qubits, target)) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab) probs = stab.probabilities() target = np.array([0.5, 0, 0, 0, 0, 0, 0, 0.5]) self.assertTrue(np.allclose(probs, target)) @@ -483,6 +630,10 @@ def test_probabilities_dict_ghz(self): probs = stab.probabilities_dict(qargs) target = {"000": 0.5, "111": 0.5} self.assertDictAlmostEqual(probs, target) + target.update( + StabilizerStateTestingTools._bitstring_product_dict(num_qubits, target) + ) + StabilizerStateTestingTools._verify_individual_bitstrings(self, target, stab, qargs) probs = stab.probabilities(qargs) target = np.array([0.5, 0, 0, 0, 0, 0, 0, 0.5]) self.assertTrue(np.allclose(probs, target)) @@ -493,6 +644,10 @@ def test_probabilities_dict_ghz(self): probs = stab.probabilities_dict(qargs) target = {"00": 0.5, "11": 0.5} self.assertDictAlmostEqual(probs, target) + target.update(StabilizerStateTestingTools._bitstring_product_dict(2, target)) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target, stab, qargs, dict_almost_equal=True + ) probs = stab.probabilities(qargs) target = np.array([0.5, 0, 0, 0.5]) self.assertTrue(np.allclose(probs, target)) @@ -503,6 +658,9 @@ def test_probabilities_dict_ghz(self): probs = stab.probabilities_dict(qargs) target = {"0": 0.5, "1": 0.5} self.assertDictAlmostEqual(probs, target) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target, stab, qargs, dict_almost_equal=True + ) probs = stab.probabilities(qargs) target = np.array([0.5, 0.5]) self.assertTrue(np.allclose(probs, target)) @@ -520,10 +678,17 @@ def test_probs_random_subsystem(self, num_qubits): stab = StabilizerState(cliff) probs = stab.probabilities(qargs) probs_dict = stab.probabilities_dict(qargs) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, probs_dict, stab, qargs + ) target = Statevector(qc).probabilities(qargs) target_dict = Statevector(qc).probabilities_dict(qargs) + Statevector(qc).probabilities_dict() self.assertTrue(np.allclose(probs, target)) self.assertDictAlmostEqual(probs_dict, target_dict) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, target_dict, stab, qargs, dict_almost_equal=True + ) @combine(num_qubits=[2, 3, 4, 5]) def test_expval_from_random_clifford(self, num_qubits): @@ -972,10 +1137,22 @@ def test_stabilizer_bell_equiv(self): # [XX, -ZZ] and [XX, YY] both generate the stabilizer group {II, XX, YY, -ZZ} self.assertTrue(cliff1.equiv(cliff2)) self.assertEqual(cliff1.probabilities_dict(), cliff2.probabilities_dict()) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, cliff1.probabilities_dict(), cliff2 + ) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, cliff2.probabilities_dict(), cliff1 + ) # [XX, ZZ] and [XX, -YY] both generate the stabilizer group {II, XX, -YY, ZZ} self.assertTrue(cliff3.equiv(cliff4)) self.assertEqual(cliff3.probabilities_dict(), cliff4.probabilities_dict()) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, cliff3.probabilities_dict(), cliff4 + ) + StabilizerStateTestingTools._verify_individual_bitstrings( + self, cliff4.probabilities_dict(), cliff3 + ) self.assertFalse(cliff1.equiv(cliff3)) self.assertFalse(cliff2.equiv(cliff4)) diff --git a/test/python/quantum_info/test_quaternions.py b/test/python/quantum_info/test_quaternions.py index 48e2ead8b894..d838ee2d1b52 100644 --- a/test/python/quantum_info/test_quaternions.py +++ b/test/python/quantum_info/test_quaternions.py @@ -92,12 +92,14 @@ def test_mul_by_quat(self): def test_mul_by_array(self): """Quaternions cannot be multiplied with an array.""" other_array = np.array([0.1, 0.2, 0.3, 0.4]) - self.assertRaises(Exception, self.quat_unnormalized.__mul__, other_array) + with self.assertRaises(TypeError): + _ = self.quat_unnormalized * other_array def test_mul_by_scalar(self): """Quaternions cannot be multiplied with a scalar.""" other_scalar = 0.123456789 - self.assertRaises(Exception, self.quat_unnormalized.__mul__, other_scalar) + with self.assertRaises(TypeError): + _ = self.quat_unnormalized * other_scalar def test_rotation(self): """Multiplication by -1 should give the same rotation.""" diff --git a/test/python/synthesis/test_clifford_sythesis.py b/test/python/synthesis/test_clifford_sythesis.py new file mode 100644 index 000000000000..887f1af5ad99 --- /dev/null +++ b/test/python/synthesis/test_clifford_sythesis.py @@ -0,0 +1,118 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 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. + +# pylint: disable=invalid-name +"""Tests for Clifford synthesis functions.""" + +import numpy as np +from ddt import ddt +from qiskit.circuit.random import random_clifford_circuit +from qiskit.quantum_info.operators import Clifford +from qiskit.synthesis.clifford import ( + synth_clifford_full, + synth_clifford_ag, + synth_clifford_bm, + synth_clifford_greedy, +) + +from test import QiskitTestCase # pylint: disable=wrong-import-order +from test import combine # pylint: disable=wrong-import-order + + +@ddt +class TestCliffordSynthesis(QiskitTestCase): + """Tests for clifford synthesis functions.""" + + @staticmethod + def _cliffords_1q(): + clifford_dicts = [ + {"stabilizer": ["+Z"], "destabilizer": ["-X"]}, + {"stabilizer": ["-Z"], "destabilizer": ["+X"]}, + {"stabilizer": ["-Z"], "destabilizer": ["-X"]}, + {"stabilizer": ["+Z"], "destabilizer": ["+Y"]}, + {"stabilizer": ["+Z"], "destabilizer": ["-Y"]}, + {"stabilizer": ["-Z"], "destabilizer": ["+Y"]}, + {"stabilizer": ["-Z"], "destabilizer": ["-Y"]}, + {"stabilizer": ["+X"], "destabilizer": ["+Z"]}, + {"stabilizer": ["+X"], "destabilizer": ["-Z"]}, + {"stabilizer": ["-X"], "destabilizer": ["+Z"]}, + {"stabilizer": ["-X"], "destabilizer": ["-Z"]}, + {"stabilizer": ["+X"], "destabilizer": ["+Y"]}, + {"stabilizer": ["+X"], "destabilizer": ["-Y"]}, + {"stabilizer": ["-X"], "destabilizer": ["+Y"]}, + {"stabilizer": ["-X"], "destabilizer": ["-Y"]}, + {"stabilizer": ["+Y"], "destabilizer": ["+X"]}, + {"stabilizer": ["+Y"], "destabilizer": ["-X"]}, + {"stabilizer": ["-Y"], "destabilizer": ["+X"]}, + {"stabilizer": ["-Y"], "destabilizer": ["-X"]}, + {"stabilizer": ["+Y"], "destabilizer": ["+Z"]}, + {"stabilizer": ["+Y"], "destabilizer": ["-Z"]}, + {"stabilizer": ["-Y"], "destabilizer": ["+Z"]}, + {"stabilizer": ["-Y"], "destabilizer": ["-Z"]}, + ] + return [Clifford.from_dict(i) for i in clifford_dicts] + + def test_decompose_1q(self): + """Test synthesis for all 1-qubit Cliffords""" + for cliff in self._cliffords_1q(): + with self.subTest(msg=f"Test circuit {cliff}"): + target = cliff + value = Clifford(cliff.to_circuit()) + self.assertEqual(target, value) + + @combine(num_qubits=[2, 3]) + def test_synth_bm(self, num_qubits): + """Test B&M synthesis for set of {num_qubits}-qubit Cliffords""" + rng = np.random.default_rng(1234) + samples = 50 + for _ in range(samples): + circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) + target = Clifford(circ) + synth_circ = synth_clifford_bm(target) + value = Clifford(synth_circ) + self.assertEqual(value, target) + + @combine(num_qubits=[2, 3, 4, 5]) + def test_synth_ag(self, num_qubits): + """Test A&G synthesis for set of {num_qubits}-qubit Cliffords""" + rng = np.random.default_rng(1234) + samples = 1 + for _ in range(samples): + circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) + target = Clifford(circ) + synth_circ = synth_clifford_ag(target) + value = Clifford(synth_circ) + self.assertEqual(value, target) + + @combine(num_qubits=[1, 2, 3, 4, 5]) + def test_synth_greedy(self, num_qubits): + """Test greedy synthesis for set of {num_qubits}-qubit Cliffords""" + rng = np.random.default_rng(1234) + samples = 50 + for _ in range(samples): + circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) + target = Clifford(circ) + synth_circ = synth_clifford_greedy(target) + value = Clifford(synth_circ) + self.assertEqual(value, target) + + @combine(num_qubits=[1, 2, 3, 4, 5]) + def test_synth_full(self, num_qubits): + """Test synthesis for set of {num_qubits}-qubit Cliffords""" + rng = np.random.default_rng(1234) + samples = 50 + for _ in range(samples): + circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) + target = Clifford(circ) + synth_circ = synth_clifford_full(target) + value = Clifford(synth_circ) + self.assertEqual(value, target) diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 9a2432b82f9c..f20b102d1838 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -126,7 +126,7 @@ class OpARepeatSynthesisPlugin(HighLevelSynthesisPlugin): """The repeat synthesis for opA""" def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): - if "n" not in options.keys(): + if "n" not in options: return None qc = QuantumCircuit(1) @@ -206,7 +206,7 @@ def __init__(self): def method_names(self, op_name): """Returns plugin methods for op_name.""" - if op_name in self.plugins_by_op.keys(): + if op_name in self.plugins_by_op: return self.plugins_by_op[op_name] else: return [] diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 247aa82ec039..c00208a2ffaf 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -71,7 +71,7 @@ def mock_get_passmanager_stage( elif stage_name == "layout": return PassManager([]) else: - raise Exception("Failure, unexpected stage plugin combo for test") + raise RuntimeError("Failure, unexpected stage plugin combo for test") def emptycircuit(): diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 1eebc25f7827..fbe4e1fbf74c 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -1346,7 +1346,7 @@ def _visit_block(circuit, qubit_mapping=None): qargs = tuple(qubit_mapping[x] for x in instruction.qubits) if not isinstance(instruction.operation, ControlFlowOp): if len(qargs) > 2 or len(qargs) < 0: - raise Exception("Invalid number of qargs for instruction") + raise RuntimeError("Invalid number of qargs for instruction") if len(qargs) == 2: self.assertIn(qargs, self.coupling_edge_set) else: diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 00fa712189f2..8c96150ae8ff 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -1506,7 +1506,7 @@ def _visit_block(circuit, qubit_mapping=None): qargs = tuple(qubit_mapping[x] for x in instruction.qubits) if not isinstance(instruction.operation, ControlFlowOp): if len(qargs) > 2 or len(qargs) < 0: - raise Exception("Invalid number of qargs for instruction") + raise RuntimeError("Invalid number of qargs for instruction") if len(qargs) == 2: self.assertIn(qargs, self.coupling_edge_set) else: diff --git a/test/python/transpiler/test_swap_strategy_router.py b/test/python/transpiler/test_swap_strategy_router.py index 4a46efd57b2c..d6ca1bde53dd 100644 --- a/test/python/transpiler/test_swap_strategy_router.py +++ b/test/python/transpiler/test_swap_strategy_router.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2022, 2023. +# (C) Copyright IBM 2022, 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 @@ -15,12 +15,14 @@ from ddt import ddt, data from qiskit.circuit import QuantumCircuit, Qubit, QuantumRegister +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler import PassManager, CouplingMap, Layout, TranspilerError from qiskit.circuit.library import PauliEvolutionGate, CXGate from qiskit.circuit.library.n_local import QAOAAnsatz from qiskit.converters import circuit_to_dag from qiskit.exceptions import QiskitError from qiskit.quantum_info import Pauli, SparsePauliOp +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import ApplyLayout @@ -562,9 +564,47 @@ def test_edge_coloring(self, edge_coloring): self.assertEqual(pm_.run(circ), expected) + def test_permutation_tracking(self): + """Test that circuit layout permutations are properly tracked in the pass property + set and returned with the output circuit.""" + + # We use the same scenario as the QAOA test above + mixer = QuantumCircuit(4) + for idx in range(4): + mixer.ry(-idx, idx) + + op = SparsePauliOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]) + circ = QAOAAnsatz(op, reps=2, mixer_operator=mixer) + + expected_swap_permutation = [3, 1, 2, 0] + expected_full_permutation = [1, 3, 2, 0] + + cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (2, 3)]) + swap_strat = SwapStrategy(cmap, swap_layers=[[(0, 1), (2, 3)], [(1, 2)]]) + + # test standalone + swap_pm = PassManager( + [ + FindCommutingPauliEvolutions(), + Commuting2qGateRouter(swap_strat), + ] + ) + swapped = swap_pm.run(circ.decompose()) + + # test as pre-routing step + backend = GenericBackendV2(num_qubits=4, coupling_map=[[0, 1], [0, 2], [0, 3]], seed=42) + pm = generate_preset_pass_manager( + optimization_level=3, target=backend.target, seed_transpiler=40 + ) + pm.pre_routing = swap_pm + full = pm.run(circ.decompose()) + + self.assertEqual(swapped.layout.routing_permutation(), expected_swap_permutation) + self.assertEqual(full.layout.routing_permutation(), expected_full_permutation) + class TestSwapRouterExceptions(QiskitTestCase): - """Test that exceptions are properly raises.""" + """Test that exceptions are properly raised.""" def setUp(self): """Setup useful variables."""