From 5f535242ff329c3a6f940cc80bac0504913b71ef Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 17 Apr 2024 20:52:07 +0100 Subject: [PATCH 001/179] Refactor Rust crates to build a single extension module (#12134) * Refactor Rust crates to build a single extension module Previously we were building three different Python extension modules out of Rust space. This was convenient for logical separation of the code, and for making the incremental compilations of the smaller crates faster, but had the disadvantage that `qasm2` and `qasm3` couldn't access the new circuit data structures directly from Rust space and had to go via Python. This modifies the crate structure to put the Rust-space acceleration logic into crates that we only build as Rust libraries, then adds another (`pyext`) crate to be the only that that actually builds as shared Python C extension. This then lets the Rust crates interact with each other (and the Rust crates still contain PyO3 Python-binding logic internally) and accept and output each others' Python types. The one Python extension is still called `qiskit._accelerate`, but it's built by the Rust crate `qiskit-pyext`. This necessitated some further changes, since `qiskit._accelerate` now contains acccelerators for lots of parts of the Qiskit package hierarchy, but needs to have a single low-level place to initialise itself: - The circuit logic `circuit` is separated out into its own separate crate so that this can form the lowest part of the Rust hierarchy. This is done so that `accelerate` with its grab-bag of accelerators from all over the place does not need to be the lowest part of the stack. Over time, it's likely that everything will start touching `circuit` anyway. - `qiskit._qasm2` and `qiskit._qasm3` respectively became `qiskit._accelerate.qasm2` and `qiskit._accelerate.qasm3`, since they no longer get their own extension modules. - `qasm3` no longer stores a Python object on itself during module initialisation that depended on `qiskit.circuit.library` to create. Now, the Python-space `qiskit.qasm3` does that and the `qasm3` crate just retrieves that at Python runtime, since that crate requires Qiskit to be importable anyway. * Fix and document Rust test harness * Fix new clippy complaints These don't appear to be new from this patch series, but changing the access modifiers on some of the crates seems to make clippy complain more vociferously about some things. * Fix pylint import order * Remove erroneous comment * Fix typos Co-authored-by: Kevin Hartman * Comment on `extension-module` * Unify naming of `qiskit-pyext` * Remove out-of-date advice on starting new crates --------- Co-authored-by: Kevin Hartman --- Cargo.lock | 71 ++++++++++------ Cargo.toml | 13 ++- crates/README.md | 64 +++++++++++++++ crates/accelerate/Cargo.toml | 10 +-- crates/accelerate/README.md | 23 ++---- .../src/euler_one_qubit_decomposer.rs | 51 ++++++------ crates/accelerate/src/lib.rs | 61 +++++--------- crates/accelerate/src/sabre/layer.rs | 8 +- crates/accelerate/src/two_qubit_decompose.rs | 21 ++--- crates/accelerate/src/utils.rs | 11 --- crates/circuit/Cargo.toml | 14 ++++ crates/circuit/README.md | 6 ++ .../src}/circuit_data.rs | 9 ++- .../src}/circuit_instruction.rs | 2 +- .../src}/intern_context.rs | 0 .../mod.rs => circuit/src/lib.rs} | 13 ++- crates/pyext/Cargo.toml | 26 ++++++ crates/pyext/README.md | 7 ++ crates/pyext/src/lib.rs | 46 +++++++++++ crates/qasm2/Cargo.toml | 8 +- crates/qasm2/src/bytecode.rs | 18 ++--- crates/qasm2/src/error.rs | 2 - crates/qasm2/src/lib.rs | 6 +- crates/qasm3/Cargo.toml | 7 +- crates/qasm3/src/circuit.rs | 2 +- crates/qasm3/src/lib.rs | 80 ++----------------- qiskit/__init__.py | 30 +++---- qiskit/circuit/controlflow/builder.py | 2 +- qiskit/circuit/library/blueprintcircuit.py | 2 +- qiskit/circuit/quantumcircuit.py | 2 +- qiskit/circuit/quantumcircuitdata.py | 4 +- qiskit/qasm2/__init__.py | 6 +- qiskit/qasm2/parse.py | 5 +- qiskit/qasm3/__init__.py | 41 +++++++++- setup.py | 16 +--- test/python/circuit/test_circuit_data.py | 2 +- 36 files changed, 393 insertions(+), 296 deletions(-) create mode 100644 crates/README.md create mode 100644 crates/circuit/Cargo.toml create mode 100644 crates/circuit/README.md rename crates/{accelerate/src/quantum_circuit => circuit/src}/circuit_data.rs (99%) rename crates/{accelerate/src/quantum_circuit => circuit/src}/circuit_instruction.rs (99%) rename crates/{accelerate/src/quantum_circuit => circuit/src}/intern_context.rs (100%) rename crates/{accelerate/src/quantum_circuit/mod.rs => circuit/src/lib.rs} (61%) create mode 100644 crates/pyext/Cargo.toml create mode 100644 crates/pyext/README.md create mode 100644 crates/pyext/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 02bf4b2bb934..6859c622967d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,9 +957,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1070,25 +1070,7 @@ dependencies = [ ] [[package]] -name = "qiskit-qasm2" -version = "1.1.0" -dependencies = [ - "hashbrown 0.14.3", - "pyo3", -] - -[[package]] -name = "qiskit-qasm3" -version = "1.1.0" -dependencies = [ - "hashbrown 0.14.3", - "indexmap 2.2.6", - "oq3_semantics", - "pyo3", -] - -[[package]] -name = "qiskit_accelerate" +name = "qiskit-accelerate" version = "1.1.0" dependencies = [ "ahash", @@ -1104,6 +1086,7 @@ dependencies = [ "numpy", "pulp", "pyo3", + "qiskit-circuit", "rand", "rand_distr", "rand_pcg", @@ -1112,6 +1095,44 @@ dependencies = [ "smallvec", ] +[[package]] +name = "qiskit-circuit" +version = "1.1.0" +dependencies = [ + "hashbrown 0.14.3", + "pyo3", +] + +[[package]] +name = "qiskit-pyext" +version = "1.1.0" +dependencies = [ + "pyo3", + "qiskit-accelerate", + "qiskit-circuit", + "qiskit-qasm2", + "qiskit-qasm3", +] + +[[package]] +name = "qiskit-qasm2" +version = "1.1.0" +dependencies = [ + "hashbrown 0.14.3", + "pyo3", + "qiskit-circuit", +] + +[[package]] +name = "qiskit-qasm3" +version = "1.1.0" +dependencies = [ + "hashbrown 0.14.3", + "indexmap 2.2.6", + "oq3_semantics", + "pyo3", +] + [[package]] name = "quote" version = "1.0.36" @@ -1312,18 +1333,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4736c3ae41c6..9c4af6260bed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,19 @@ license = "Apache-2.0" [workspace.dependencies] indexmap.version = "2.2.6" hashbrown.version = "0.14.0" -# This doesn't set `extension-module` as a shared feature because we need to be able to disable it -# during Rust-only testing (see # https://github.com/PyO3/pyo3/issues/340). +# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an +# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python +# distributions). We only activate that feature when building the C extension module; we still need +# it disabled for Rust-only tests to avoid linker errors with it not being loaded. See +# https://pyo3.rs/main/features#extension-module for more. pyo3 = { version = "0.21.2", features = ["abi3-py38"] } +# These are our own crates. +qiskit-accelerate = { path = "crates/accelerate" } +qiskit-circuit = { path = "crates/circuit" } +qiskit-qasm2 = { path = "crates/qasm2" } +qiskit-qasm3 = { path = "crates/qasm3" } + [profile.release] lto = 'fat' codegen-units = 1 diff --git a/crates/README.md b/crates/README.md new file mode 100644 index 000000000000..cbe58afa07d1 --- /dev/null +++ b/crates/README.md @@ -0,0 +1,64 @@ +## Crate structure + +All the crates in here are called `qiskit-*`, and are stored in directories that omit the `qiskit-`. + +This crate structure currently serves the purpose of building only a single Python extension module, which still separates out some of the Rust code into separate logical chunks. +The intention is that (much) longer term, we might be wanting to expose more of Qiskit functionality directly for other languages to interact with without going through Python. + +* `qiskit-pyext` is the only crate that actually builds a Python C extension library. + This is kind of like the parent crate of all the others, from an FFI perspective; others define `pyclass`es and `pyfunction`s and the like, but it's `qiskit-pyext` that builds the C extension. + Our C extension is built as `qiskit._accelerate` in Python space. +* `qiskit-accelerate` is a catch-all crate for one-off accelerators. + If what you're working on is small and largely self-contained, you probably just want to put it in here, then bind it to the C extension module in `qiskit-pyext`. +* `qiskit-circuit` is a base crate defining the Rust-space circuit objects. + This is one of the lowest points of the stack; everything that builds or works with circuits from Rust space depends on this. +* `qiskit-qasm2` is the OpenQASM 2 parser. + This depends on `qiskit-circuit`, but is otherwise pretty standalone, and it's unlikely that other things will need to interact with it. +* `qiskit-qasm3` is the Qiskit-specific side of the OpenQASM 3 importer. + The actual parser lives at https://github.com/Qiskit/openqasm3_parser, and is its own set of Rust-only crates. + +We use a structure with several crates in it for a couple of reasons: + +* logical separation of code +* faster incremental compile times + +When we're doing Rust/Python interaction, though, we have to be careful. +Pure-Rust FFI with itself over dynamic-library boundaries (like a Python C extension) isn't very natural, since Rust heavily prefers static linking. +If we had more than one Python C extension, it would be very hard to interact between the code in them. +This would be a particular problem for defining the circuit object and using it in other places, which is something we absolutely need to do. + +## Developer notes + +### Beware of initialisation order + +The Qiskit C extension `qiskit._accelerate` needs to be initialised in a single go. +It is the lowest part of the Python package stack, so it cannot rely on importing other parts of the Python library at initialisation time (except for exceptions through PyO3's `import_exception!` mechanism). +This is because, unlike pure-Python modules, the initialisation of `_accelerate` cannot be done partially, and many components of Qiskit import their accelerators from `_accelerate`. + +In general, this should not be too onerous a requirement, but if you violate it, you might see Rust panics on import, and PyO3 should wrap that up into an exception. +You might be able to track down the Rust source of the import cycle by running the import with the environment variable `RUST_BACKTRACE=full`. + + +### Tests + +Most of our functionality is tested through the Python-space tests of the `qiskit` Python package, since the Rust implementations are (to begin with) just private implementation details. +However, where it's more useful, Rust crates can also include Rust-space tests within themselves. + +Each of the Rust crates disables `doctest` because our documentation tends to be written in Sphinx's rST rather than Markdown, so `rustdoc` has a habit of thinking various random bits of syntax are codeblocks. +We can revisit that if we start writing crates that we intend to publish. + +#### Running the tests + +You need to make sure that the tests can build in standalone mode, i.e. that they link in `libpython`. +By default, we activate PyO3's `extension-module` feature in `crates/pyext`, so you have to run with the tests with that disabled: + +```bash +cargo test --no-default-features +``` + +On Linux, you might find that the dynamic linker still can't find `libpython`. +If so, you can extend the environment variable `LD_LIBRARY_PATH` to include it: + +```bash +export LD_LIBRARY_PATH="$(python -c 'import sys; print(sys.base_prefix)')/lib:$LD_LIBRARY_PATH" +``` diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 36fc5d408435..05ca3f5b6395 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "qiskit_accelerate" +name = "qiskit-accelerate" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -7,12 +7,7 @@ license.workspace = true [lib] name = "qiskit_accelerate" -crate-type = ["cdylib"] - -[features] -# This is a test-only shim removable feature. See the root `Cargo.toml`. -default = ["extension-module"] -extension-module = ["pyo3/extension-module"] +doctest = false [dependencies] rayon = "1.10" @@ -26,6 +21,7 @@ num-complex = "0.4" num-bigint = "0.4" rustworkx-core = "0.14" faer = "0.18.2" +qiskit-circuit.workspace = true [dependencies.smallvec] version = "1.13" diff --git a/crates/accelerate/README.md b/crates/accelerate/README.md index 65f4eebe5844..f039a570fc29 100644 --- a/crates/accelerate/README.md +++ b/crates/accelerate/README.md @@ -1,21 +1,10 @@ -# `qiskit._accelerate` +# `qiskit-accelerate` -This crate provides a bits-and-pieces Python extension module for small, self-contained functions +This crate provides a bits-and-pieces Rust libary for small, self-contained functions that are used by the main Python-space components to accelerate certain tasks. If you're trying to speed up one particular Python function by replacing its innards with a Rust one, this is the best -place to put the code. This is _usually_ the right place to put Rust/Python interfacing code. +place to put the code. This is _usually_ the right place to put new Rust/Python code. -The crate is made accessible as a private submodule, `qiskit._accelerate`. There are submodules -within that (largely matching the structure of the Rust code) mostly for grouping similar functions. - -Some examples of when it might be more appropriate to start a new crate instead of using the -ready-made solution of `qiskit._accelerate`: - -* The feature you are developing will have a large amount of domain-specific Rust code and is a - large self-contained module. If it reasonably works in a single Rust file, you probably just want - to put it here. - -* The Rust code is for re-use within other Qiskit crates and maintainability of the code will be - helped by using the crate system to provide API boundaries between the different sections. - -* You want to start writing your own procedural macros. +The `qiskit-pyext` crate is what actually builds the C extension modules. Modules in here should define +themselves has being submodules of `qiskit._accelerate`, and then the `qiskit-pyext` crate should bind them +into its `fn _accelerate` when it's making the C extension. diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index de588c066301..1fd5fd7834ff 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -19,6 +19,7 @@ use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; use std::f64::consts::PI; use std::ops::Deref; +use std::str::FromStr; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; @@ -30,7 +31,7 @@ use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; -use crate::utils::SliceOrInt; +use qiskit_circuit::SliceOrInt; pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; @@ -604,27 +605,31 @@ impl EulerBasis { } #[new] - pub fn from_str(input: &str) -> PyResult { - let res = match input { - "U321" => EulerBasis::U321, - "U3" => EulerBasis::U3, - "U" => EulerBasis::U, - "PSX" => EulerBasis::PSX, - "ZSX" => EulerBasis::ZSX, - "ZSXX" => EulerBasis::ZSXX, - "U1X" => EulerBasis::U1X, - "RR" => EulerBasis::RR, - "ZYZ" => EulerBasis::ZYZ, - "ZXZ" => EulerBasis::ZXZ, - "XYX" => EulerBasis::XYX, - "XZX" => EulerBasis::XZX, - basis => { - return Err(PyValueError::new_err(format!( - "Invalid target basis '{basis}'" - ))); - } - }; - Ok(res) + pub fn __new__(input: &str) -> PyResult { + Self::from_str(input) + .map_err(|_| PyValueError::new_err(format!("Invalid target basis '{input}'"))) + } +} + +impl FromStr for EulerBasis { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "U321" => Ok(EulerBasis::U321), + "U3" => Ok(EulerBasis::U3), + "U" => Ok(EulerBasis::U), + "PSX" => Ok(EulerBasis::PSX), + "ZSX" => Ok(EulerBasis::ZSX), + "ZSXX" => Ok(EulerBasis::ZSXX), + "U1X" => Ok(EulerBasis::U1X), + "RR" => Ok(EulerBasis::RR), + "ZYZ" => Ok(EulerBasis::ZYZ), + "ZXZ" => Ok(EulerBasis::ZXZ), + "XYX" => Ok(EulerBasis::XYX), + "XZX" => Ok(EulerBasis::XZX), + _ => Err(()), + } } } @@ -714,7 +719,7 @@ pub fn unitary_to_gate_sequence( ) -> PyResult> { let mut target_basis_vec: Vec = Vec::with_capacity(target_basis_list.len()); for basis in target_basis_list { - let basis_enum = EulerBasis::from_str(basis.deref())?; + let basis_enum = EulerBasis::__new__(basis.deref())?; target_basis_vec.push(basis_enum) } let unitary_mat = unitary.as_array(); diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 106764d4c0fb..50f69897757b 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -12,26 +12,24 @@ use std::env; -use pyo3::prelude::*; -use pyo3::wrap_pymodule; +use pyo3::import_exception; -mod convert_2q_block_matrix; -mod dense_layout; -mod edge_collections; -mod error_map; -mod euler_one_qubit_decomposer; -mod nlayout; -mod optimize_1q_gates; -mod pauli_exp_val; -mod quantum_circuit; -mod results; -mod sabre; -mod sampled_exp_val; -mod sparse_pauli_op; -mod stochastic_swap; -mod two_qubit_decompose; -mod utils; -mod vf2_layout; +pub mod convert_2q_block_matrix; +pub mod dense_layout; +pub mod edge_collections; +pub mod error_map; +pub mod euler_one_qubit_decomposer; +pub mod nlayout; +pub mod optimize_1q_gates; +pub mod pauli_exp_val; +pub mod results; +pub mod sabre; +pub mod sampled_exp_val; +pub mod sparse_pauli_op; +pub mod stochastic_swap; +pub mod two_qubit_decompose; +pub mod utils; +pub mod vf2_layout; #[inline] pub fn getenv_use_multiple_threads() -> bool { @@ -46,27 +44,4 @@ pub fn getenv_use_multiple_threads() -> bool { !parallel_context || force_threads } -#[pymodule] -fn _accelerate(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pymodule!(nlayout::nlayout))?; - m.add_wrapped(wrap_pymodule!(stochastic_swap::stochastic_swap))?; - m.add_wrapped(wrap_pymodule!(sabre::sabre))?; - m.add_wrapped(wrap_pymodule!(pauli_exp_val::pauli_expval))?; - m.add_wrapped(wrap_pymodule!(dense_layout::dense_layout))?; - m.add_wrapped(wrap_pymodule!(quantum_circuit::quantum_circuit))?; - m.add_wrapped(wrap_pymodule!(error_map::error_map))?; - m.add_wrapped(wrap_pymodule!(sparse_pauli_op::sparse_pauli_op))?; - m.add_wrapped(wrap_pymodule!(results::results))?; - m.add_wrapped(wrap_pymodule!(optimize_1q_gates::optimize_1q_gates))?; - m.add_wrapped(wrap_pymodule!(sampled_exp_val::sampled_exp_val))?; - m.add_wrapped(wrap_pymodule!(vf2_layout::vf2_layout))?; - m.add_wrapped(wrap_pymodule!(two_qubit_decompose::two_qubit_decompose))?; - m.add_wrapped(wrap_pymodule!(utils::utils))?; - m.add_wrapped(wrap_pymodule!( - euler_one_qubit_decomposer::euler_one_qubit_decomposer - ))?; - m.add_wrapped(wrap_pymodule!( - convert_2q_block_matrix::convert_2q_block_matrix - ))?; - Ok(()) -} +import_exception!(qiskit.exceptions, QiskitError); diff --git a/crates/accelerate/src/sabre/layer.rs b/crates/accelerate/src/sabre/layer.rs index 42be0045c9d3..8874a375935f 100644 --- a/crates/accelerate/src/sabre/layer.rs +++ b/crates/accelerate/src/sabre/layer.rs @@ -182,7 +182,7 @@ impl ExtendedSet { /// Calculate the score of applying the given swap, relative to not applying it. pub fn score(&self, swap: [PhysicalQubit; 2], dist: &ArrayView2) -> f64 { - if self.len == 0 { + if self.is_empty() { return 0.0; } let [a, b] = swap; @@ -206,7 +206,7 @@ impl ExtendedSet { /// Calculate the total absolute score of this set of nodes over the given layout. pub fn total_score(&self, dist: &ArrayView2) -> f64 { - if self.len == 0 { + if self.is_empty() { return 0.0; } self.qubits @@ -232,6 +232,10 @@ impl ExtendedSet { self.len } + pub fn is_empty(&self) -> bool { + self.len == 0 + } + /// Apply a physical swap to the current layout data structure. pub fn apply_swap(&mut self, swap: [PhysicalQubit; 2]) { let [a, b] = swap; diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index de3b80970f3c..7dcb273ac163 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -22,7 +22,6 @@ use approx::{abs_diff_eq, relative_eq}; use num_complex::{Complex, Complex64, ComplexFloat}; use num_traits::Zero; use pyo3::exceptions::{PyIndexError, PyValueError}; -use pyo3::import_exception; use pyo3::prelude::*; use pyo3::wrap_pyfunction; use pyo3::Python; @@ -46,11 +45,14 @@ use crate::euler_one_qubit_decomposer::{ OneQubitGateSequence, ANGLE_ZERO_EPSILON, }; use crate::utils; +use crate::QiskitError; use rand::prelude::*; use rand_distr::StandardNormal; use rand_pcg::Pcg64Mcg; +use qiskit_circuit::SliceOrInt; + const PI2: f64 = PI / 2.0; const PI4: f64 = PI / 4.0; const PI32: f64 = 3.0 * PI2; @@ -200,7 +202,6 @@ fn decompose_two_qubit_product_gate( det_r = det_one_qubit(r.view()); } if det_r.abs() < 0.1 { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err( "decompose_two_qubit_product_gate: unable to decompose: detR < 0.1", )); @@ -213,7 +214,6 @@ fn decompose_two_qubit_product_gate( let mut l = temp.slice(s![..;2, ..;2]).to_owned(); let det_l = det_one_qubit(l.view()); if det_l.abs() < 0.9 { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err( "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9", )); @@ -692,7 +692,6 @@ impl TwoQubitWeylDecomposition { } } if !found { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err(format!( "TwoQubitWeylDecomposition: failed to diagonalize M2. Please report this at https://github.com/Qiskit/qiskit-terra/issues/4159. Input: {:?}", unitary_matrix ))); @@ -1083,7 +1082,6 @@ impl TwoQubitWeylDecomposition { specialized.calculated_fidelity = tr.trace_to_fid(); if let Some(fid) = specialized.requested_fidelity { if specialized.calculated_fidelity + 1.0e-13 < fid { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err(format!( "Specialization: {:?} calculated fidelity: {} is worse than requested fidelity: {}", specialized.specialization, @@ -1133,7 +1131,7 @@ impl TwoQubitWeylDecomposition { atol: Option, ) -> PyResult { let euler_basis: EulerBasis = match euler_basis { - Some(basis) => EulerBasis::from_str(basis.deref())?, + Some(basis) => EulerBasis::__new__(basis.deref())?, None => self.default_euler_basis, }; let target_1q_basis_list: Vec = vec![euler_basis]; @@ -1237,9 +1235,9 @@ impl TwoQubitGateSequence { Ok(self.gates.len()) } - fn __getitem__(&self, py: Python, idx: utils::SliceOrInt) -> PyResult { + fn __getitem__(&self, py: Python, idx: SliceOrInt) -> PyResult { match idx { - utils::SliceOrInt::Slice(slc) => { + SliceOrInt::Slice(slc) => { let len = self.gates.len().try_into().unwrap(); let indices = slc.indices(len)?; let mut out_vec: TwoQubitSequenceVec = Vec::new(); @@ -1266,7 +1264,7 @@ impl TwoQubitGateSequence { } Ok(out_vec.into_py(py)) } - utils::SliceOrInt::Int(idx) => { + SliceOrInt::Int(idx) => { let len = self.gates.len() as isize; if idx >= len || idx < -len { Err(PyIndexError::new_err(format!("Invalid index, {idx}"))) @@ -1617,7 +1615,6 @@ impl TwoQubitBasisDecomposer { EulerBasis::ZSXX => (), _ => { if self.pulse_optimize.is_some() { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err(format!( "'pulse_optimize' currently only works with ZSX basis ({} used)", self.euler_basis.as_str() @@ -1629,7 +1626,6 @@ impl TwoQubitBasisDecomposer { } if self.gate != "cx" { if self.pulse_optimize.is_some() { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err( "pulse_optimizer currently only works with CNOT entangling gate", )); @@ -1645,7 +1641,6 @@ impl TwoQubitBasisDecomposer { None }; if self.pulse_optimize.is_some() && res.is_none() { - import_exception!(qiskit, QiskitError); return Err(QiskitError::new_err( "Failed to compute requested pulse optimal decomposition", )); @@ -1822,7 +1817,7 @@ impl TwoQubitBasisDecomposer { Ok(TwoQubitBasisDecomposer { gate, basis_fidelity, - euler_basis: EulerBasis::from_str(euler_basis)?, + euler_basis: EulerBasis::__new__(euler_basis)?, pulse_optimize, basis_decomposer, super_controlled, diff --git a/crates/accelerate/src/utils.rs b/crates/accelerate/src/utils.rs index 9cf8a4f513e2..6df00f7f8b76 100644 --- a/crates/accelerate/src/utils.rs +++ b/crates/accelerate/src/utils.rs @@ -11,22 +11,11 @@ // that they have been altered from the originals. use pyo3::prelude::*; -use pyo3::types::PySlice; use faer_ext::IntoFaerComplex; use num_complex::Complex; use numpy::{IntoPyArray, PyReadonlyArray2}; -/// A private enumeration type used to extract arguments to pymethod -/// that may be either an index or a slice -#[derive(FromPyObject)] -pub enum SliceOrInt<'a> { - // The order here defines the order the variants are tried in the FromPyObject` derivation. - // `Int` is _much_ more common, so that should be first. - Int(isize), - Slice(Bound<'a, PySlice>), -} - /// Return indices that sort partially ordered data. /// If `data` contains two elements that are incomparable, /// an error will be thrown. diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml new file mode 100644 index 000000000000..6ec38392cc38 --- /dev/null +++ b/crates/circuit/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "qiskit-circuit" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lib] +name = "qiskit_circuit" +doctest = false + +[dependencies] +hashbrown.workspace = true +pyo3.workspace = true diff --git a/crates/circuit/README.md b/crates/circuit/README.md new file mode 100644 index 000000000000..b9375c9f99da --- /dev/null +++ b/crates/circuit/README.md @@ -0,0 +1,6 @@ +# `qiskit-circuit` + +The Rust-based data structures for circuits. +This currently defines the core data collections for `QuantumCircuit`, but may expand in the future to back `DAGCircuit` as well. + +This crate is a very low part of the Rust stack, if not the very lowest. diff --git a/crates/accelerate/src/quantum_circuit/circuit_data.rs b/crates/circuit/src/circuit_data.rs similarity index 99% rename from crates/accelerate/src/quantum_circuit/circuit_data.rs rename to crates/circuit/src/circuit_data.rs index 823ce8c4634e..07bab2c17c9b 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -10,9 +10,10 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::quantum_circuit::circuit_instruction::CircuitInstruction; -use crate::quantum_circuit::intern_context::{BitType, IndexType, InternContext}; -use crate::utils::SliceOrInt; +use crate::circuit_instruction::CircuitInstruction; +use crate::intern_context::{BitType, IndexType, InternContext}; +use crate::SliceOrInt; + use hashbrown::HashMap; use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; @@ -130,7 +131,7 @@ impl Eq for BitAsKey {} /// Raises: /// KeyError: if ``data`` contains a reference to a bit that is not present /// in ``qubits`` or ``clbits``. -#[pyclass(sequence, module = "qiskit._accelerate.quantum_circuit")] +#[pyclass(sequence, module = "qiskit._accelerate.circuit")] #[derive(Clone, Debug)] pub struct CircuitData { /// The packed instruction listing. diff --git a/crates/accelerate/src/quantum_circuit/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs similarity index 99% rename from crates/accelerate/src/quantum_circuit/circuit_instruction.rs rename to crates/circuit/src/circuit_instruction.rs index 8dc9a5d1d21a..48b0c4d20eee 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -51,7 +51,7 @@ use pyo3::{PyObject, PyResult}; freelist = 20, sequence, get_all, - module = "qiskit._accelerate.quantum_circuit" + module = "qiskit._accelerate.circuit" )] #[derive(Clone, Debug)] pub struct CircuitInstruction { diff --git a/crates/accelerate/src/quantum_circuit/intern_context.rs b/crates/circuit/src/intern_context.rs similarity index 100% rename from crates/accelerate/src/quantum_circuit/intern_context.rs rename to crates/circuit/src/intern_context.rs diff --git a/crates/accelerate/src/quantum_circuit/mod.rs b/crates/circuit/src/lib.rs similarity index 61% rename from crates/accelerate/src/quantum_circuit/mod.rs rename to crates/circuit/src/lib.rs index 2b94619c6f39..cd560bad7387 100644 --- a/crates/accelerate/src/quantum_circuit/mod.rs +++ b/crates/circuit/src/lib.rs @@ -15,9 +15,20 @@ pub mod circuit_instruction; pub mod intern_context; use pyo3::prelude::*; +use pyo3::types::PySlice; + +/// A private enumeration type used to extract arguments to pymethod +/// that may be either an index or a slice +#[derive(FromPyObject)] +pub enum SliceOrInt<'a> { + // The order here defines the order the variants are tried in the FromPyObject` derivation. + // `Int` is _much_ more common, so that should be first. + Int(isize), + Slice(Bound<'a, PySlice>), +} #[pymodule] -pub fn quantum_circuit(m: &Bound) -> PyResult<()> { +pub fn circuit(m: Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; Ok(()) diff --git a/crates/pyext/Cargo.toml b/crates/pyext/Cargo.toml new file mode 100644 index 000000000000..daaf19e1f6a4 --- /dev/null +++ b/crates/pyext/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "qiskit-pyext" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lib] +name = "qiskit_pyext" +doctest = false +crate-type = ["cdylib"] + +[features] +# We always need to activate PyO3's `extension-module` for this crate to be useful at all, but we +# need it *not* to be active for the tests of other crates to work. If the feature is active, +# `libpython` is *not* linked, and if the feature isn't present, then it is. To test the Rust +# crates as standalone binaries, executables, we need `libpython` to be linked in, so we make the +# feature a default, and run `cargo test --no-default-features` to turn it off. +default = ["pyo3/extension-module"] + +[dependencies] +pyo3.workspace = true +qiskit-accelerate.workspace = true +qiskit-circuit.workspace = true +qiskit-qasm2.workspace = true +qiskit-qasm3.workspace = true diff --git a/crates/pyext/README.md b/crates/pyext/README.md new file mode 100644 index 000000000000..ac86d9fd7fe8 --- /dev/null +++ b/crates/pyext/README.md @@ -0,0 +1,7 @@ +# `qiskit-pyext` + +This is the Rust crate that actually builds the `qiskit._accelerate` Python extension module. + +See the README at the `crates` top level for more information on the structure. +Any self-contained submodule crates (e.g. `qasm2`) can define their own `pymodule`, but they should compile only as an rlib, and this crate should then build them into the top-level `qiskit._accelerate`. +This module is also responsible for rewrapping all of the bits-and-pieces parts of `crates/accerelate` into the `qiskit._accelerate` extension module. diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs new file mode 100644 index 000000000000..b7c89872bf8d --- /dev/null +++ b/crates/pyext/src/lib.rs @@ -0,0 +1,46 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::prelude::*; +use pyo3::wrap_pymodule; + +use qiskit_accelerate::{ + convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, + error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, nlayout::nlayout, + optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, results::results, + sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, + stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, utils::utils, + vf2_layout::vf2_layout, +}; + +#[pymodule] +fn _accelerate(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(qiskit_circuit::circuit))?; + m.add_wrapped(wrap_pymodule!(qiskit_qasm2::qasm2))?; + m.add_wrapped(wrap_pymodule!(qiskit_qasm3::qasm3))?; + m.add_wrapped(wrap_pymodule!(convert_2q_block_matrix))?; + m.add_wrapped(wrap_pymodule!(dense_layout))?; + m.add_wrapped(wrap_pymodule!(error_map))?; + m.add_wrapped(wrap_pymodule!(euler_one_qubit_decomposer))?; + m.add_wrapped(wrap_pymodule!(nlayout))?; + m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?; + m.add_wrapped(wrap_pymodule!(pauli_expval))?; + m.add_wrapped(wrap_pymodule!(results))?; + m.add_wrapped(wrap_pymodule!(sabre))?; + m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; + m.add_wrapped(wrap_pymodule!(sparse_pauli_op))?; + m.add_wrapped(wrap_pymodule!(stochastic_swap))?; + m.add_wrapped(wrap_pymodule!(two_qubit_decompose))?; + m.add_wrapped(wrap_pymodule!(utils))?; + m.add_wrapped(wrap_pymodule!(vf2_layout))?; + Ok(()) +} diff --git a/crates/qasm2/Cargo.toml b/crates/qasm2/Cargo.toml index bbeb057f20a4..68137ee602bf 100644 --- a/crates/qasm2/Cargo.toml +++ b/crates/qasm2/Cargo.toml @@ -7,13 +7,9 @@ license.workspace = true [lib] name = "qiskit_qasm2" -crate-type = ["cdylib"] - -[features] -# This is a test-only shim removable feature. See the root `Cargo.toml`. -default = ["extension-module"] -extension-module = ["pyo3/extension-module"] +doctest = false [dependencies] hashbrown.workspace = true pyo3.workspace = true +qiskit-circuit.workspace = true diff --git a/crates/qasm2/src/bytecode.rs b/crates/qasm2/src/bytecode.rs index f586824696a6..2dea3a2b0e85 100644 --- a/crates/qasm2/src/bytecode.rs +++ b/crates/qasm2/src/bytecode.rs @@ -21,7 +21,7 @@ use crate::{CustomClassical, CustomInstruction}; /// The Rust parser produces an iterator of these `Bytecode` instructions, which comprise an opcode /// integer for operation distinction, and a free-form tuple containing the operands. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct Bytecode { #[pyo3(get)] @@ -31,7 +31,7 @@ pub struct Bytecode { } /// The operations that are represented by the "bytecode" passed to Python. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub enum OpCode { // There is only a `Gate` here, not a `GateInBasis`, because in Python space we don't have the @@ -60,7 +60,7 @@ pub enum OpCode { // that makes things a little fiddlier with PyO3, and there's no real benefit for our uses. /// A (potentially folded) floating-point constant value as part of an expression. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct ExprConstant { #[pyo3(get)] @@ -68,7 +68,7 @@ pub struct ExprConstant { } /// A reference to one of the arguments to the gate. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct ExprArgument { #[pyo3(get)] @@ -77,7 +77,7 @@ pub struct ExprArgument { /// A unary operation acting on some other part of the expression tree. This includes the `+` and /// `-` unary operators, but also any of the built-in scientific-calculator functions. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct ExprUnary { #[pyo3(get)] @@ -87,7 +87,7 @@ pub struct ExprUnary { } /// A binary operation acting on two other parts of the expression tree. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct ExprBinary { #[pyo3(get)] @@ -99,7 +99,7 @@ pub struct ExprBinary { } /// Some custom callable Python function that the user told us about. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub struct ExprCustom { #[pyo3(get)] @@ -112,7 +112,7 @@ pub struct ExprCustom { /// each of these, but this way involves fewer imports in Python, and also serves to split up the /// option tree at the top level, so we don't have to test every unary operator before testing /// other operations. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub enum UnaryOpCode { Negate, @@ -128,7 +128,7 @@ pub enum UnaryOpCode { /// each of these, but this way involves fewer imports in Python, and also serves to split up the /// option tree at the top level, so we don't have to test every binary operator before testing /// other operations. -#[pyclass(module = "qiskit._qasm2", frozen)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] #[derive(Clone)] pub enum BinaryOpCode { Add, diff --git a/crates/qasm2/src/error.rs b/crates/qasm2/src/error.rs index 4cfe6bc42636..6b34364a2abe 100644 --- a/crates/qasm2/src/error.rs +++ b/crates/qasm2/src/error.rs @@ -79,6 +79,4 @@ pub fn message_bad_eof(position: Option<&Position>, required: &str) -> String { ) } -// We define the exception in Python space so it can inherit from QiskitError; it's easier to do -// that from Python and wrap rather than also needing to import QiskitError to Rust to wrap. import_exception!(qiskit.qasm2.exceptions, QASM2ParseError); diff --git a/crates/qasm2/src/lib.rs b/crates/qasm2/src/lib.rs index 1731330d30c7..7129b17f7fdf 100644 --- a/crates/qasm2/src/lib.rs +++ b/crates/qasm2/src/lib.rs @@ -124,7 +124,7 @@ fn bytecode_from_file( /// output. The principal entry points for Python are :func:`bytecode_from_string` and /// :func:`bytecode_from_file`, which produce iterables of :class:`Bytecode` objects. #[pymodule] -fn _qasm2(module: &Bound<'_, PyModule>) -> PyResult<()> { +pub fn qasm2(module: &Bound) -> PyResult<()> { module.add_class::()?; module.add_class::()?; module.add_class::()?; @@ -136,10 +136,6 @@ fn _qasm2(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_class::()?; module.add_class::()?; module.add_class::()?; - module.add( - "QASM2ParseError", - module.py().get_type_bound::(), - )?; module.add_function(wrap_pyfunction!(bytecode_from_string, module)?)?; module.add_function(wrap_pyfunction!(bytecode_from_file, module)?)?; Ok(()) diff --git a/crates/qasm3/Cargo.toml b/crates/qasm3/Cargo.toml index c48d0e9fa378..a8e20d13d58c 100644 --- a/crates/qasm3/Cargo.toml +++ b/crates/qasm3/Cargo.toml @@ -7,12 +7,7 @@ license.workspace = true [lib] name = "qiskit_qasm3" -crate-type = ["cdylib"] - -[features] -# This is a test-only shim removable feature. See the root `Cargo.toml`. -default = ["extension-module"] -extension-module = ["pyo3/extension-module"] +doctest = false [dependencies] pyo3.workspace = true diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index 1293b22580b0..747980819a0e 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -75,7 +75,7 @@ register_type!(PyClassicalRegister); /// Information received from Python space about how to construct a Python-space object to /// represent a given gate that might be declared. -#[pyclass(module = "qiskit._qasm3", frozen, name = "CustomGate")] +#[pyclass(module = "qiskit._accelerate.qasm3", frozen, name = "CustomGate")] #[derive(Clone, Debug)] pub struct PyGate { constructor: Py, diff --git a/crates/qasm3/src/lib.rs b/crates/qasm3/src/lib.rs index 12ff4c88a558..9a651a5c12e2 100644 --- a/crates/qasm3/src/lib.rs +++ b/crates/qasm3/src/lib.rs @@ -22,17 +22,13 @@ use std::path::{Path, PathBuf}; use hashbrown::HashMap; use pyo3::prelude::*; -use pyo3::types::{PyModule, PyTuple}; +use pyo3::types::PyModule; use oq3_semantics::syntax_to_semantics::parse_source_string; use pyo3::pybacked::PyBackedStr; use crate::error::QASM3ImporterError; -/// The name of a Python attribute to define on the given module where the default implementation -/// of the ``stdgates.inc`` custom instructions is located. -const STDGATES_INC_CUSTOM_GATES_ATTR: &str = "STDGATES_INC_GATES"; - /// Load an OpenQASM 3 program from a string into a :class:`.QuantumCircuit`. /// /// .. warning:: @@ -58,16 +54,15 @@ const STDGATES_INC_CUSTOM_GATES_ATTR: &str = "STDGATES_INC_GATES"; /// In the case of a parsing error, most of the error messages are printed to the terminal /// and formatted, for better legibility. #[pyfunction] -#[pyo3(pass_module, signature = (source, /, *, custom_gates=None, include_path=None))] +#[pyo3(signature = (source, /, *, custom_gates=None, include_path=None))] pub fn loads( - module: &Bound, py: Python, source: String, custom_gates: Option>, include_path: Option>, ) -> PyResult { let default_include_path = || -> PyResult> { - let filename: PyBackedStr = module.filename()?.try_into()?; + let filename: PyBackedStr = py.import_bound("qiskit")?.filename()?.try_into()?; Ok(vec![Path::new(filename.deref()) .parent() .unwrap() @@ -87,8 +82,9 @@ pub fn loads( .into_iter() .map(|gate| (gate.name().to_owned(), gate)) .collect(), - None => module - .getattr(STDGATES_INC_CUSTOM_GATES_ATTR)? + None => py + .import_bound("qiskit.qasm3")? + .getattr("STDGATES_INC_GATES")? .iter()? .map(|obj| { let gate = obj?.extract::()?; @@ -129,11 +125,9 @@ pub fn loads( /// and formatted, for better legibility. #[pyfunction] #[pyo3( - pass_module, signature = (pathlike_or_filelike, /, *, custom_gates=None, include_path=None), )] pub fn load( - module: &Bound, py: Python, pathlike_or_filelike: &Bound, custom_gates: Option>, @@ -154,73 +148,15 @@ pub fn load( QASM3ImporterError::new_err(format!("failed to read file '{:?}': {:?}", &path, err)) })? }; - loads(module, py, source, custom_gates, include_path) -} - -/// Create a suitable sequence for use with the ``custom_gates`` of :func:`load` and :func:`loads`, -/// as a Python object on the Python heap, so we can re-use it, and potentially expose it has a -/// data attribute to users. -fn stdgates_inc_gates(py: Python) -> PyResult> { - let library = PyModule::import_bound(py, "qiskit.circuit.library")?; - let stdlib_gate = |qiskit_class, name, num_params, num_qubits| -> PyResult> { - Ok(circuit::PyGate::new( - py, - library.getattr(qiskit_class)?, - name, - num_params, - num_qubits, - ) - .into_py(py)) - }; - Ok(PyTuple::new_bound( - py, - vec![ - stdlib_gate("PhaseGate", "p", 1, 1)?, - stdlib_gate("XGate", "x", 0, 1)?, - stdlib_gate("YGate", "y", 0, 1)?, - stdlib_gate("ZGate", "z", 0, 1)?, - stdlib_gate("HGate", "h", 0, 1)?, - stdlib_gate("SGate", "s", 0, 1)?, - stdlib_gate("SdgGate", "sdg", 0, 1)?, - stdlib_gate("TGate", "t", 0, 1)?, - stdlib_gate("TdgGate", "tdg", 0, 1)?, - stdlib_gate("SXGate", "sx", 0, 1)?, - stdlib_gate("RXGate", "rx", 1, 1)?, - stdlib_gate("RYGate", "ry", 1, 1)?, - stdlib_gate("RZGate", "rz", 1, 1)?, - stdlib_gate("CXGate", "cx", 0, 2)?, - stdlib_gate("CYGate", "cy", 0, 2)?, - stdlib_gate("CZGate", "cz", 0, 2)?, - stdlib_gate("CPhaseGate", "cp", 1, 2)?, - stdlib_gate("CRXGate", "crx", 1, 2)?, - stdlib_gate("CRYGate", "cry", 1, 2)?, - stdlib_gate("CRZGate", "crz", 1, 2)?, - stdlib_gate("CHGate", "ch", 0, 2)?, - stdlib_gate("SwapGate", "swap", 0, 2)?, - stdlib_gate("CCXGate", "ccx", 0, 3)?, - stdlib_gate("CSwapGate", "cswap", 0, 3)?, - stdlib_gate("CUGate", "cu", 4, 2)?, - stdlib_gate("CXGate", "CX", 0, 2)?, - stdlib_gate("PhaseGate", "phase", 1, 1)?, - stdlib_gate("CPhaseGate", "cphase", 1, 2)?, - stdlib_gate("IGate", "id", 0, 1)?, - stdlib_gate("U1Gate", "u1", 1, 1)?, - stdlib_gate("U2Gate", "u2", 2, 1)?, - stdlib_gate("U3Gate", "u3", 3, 1)?, - ], - )) + loads(py, source, custom_gates, include_path) } /// Internal module supplying the OpenQASM 3 import capabilities. The entries in it should largely /// be re-exposed directly to public Python space. #[pymodule] -fn _qasm3(module: &Bound) -> PyResult<()> { +pub fn qasm3(module: &Bound) -> PyResult<()> { module.add_function(wrap_pyfunction!(loads, module)?)?; module.add_function(wrap_pyfunction!(load, module)?)?; module.add_class::()?; - module.add( - STDGATES_INC_CUSTOM_GATES_ATTR, - stdgates_inc_gates(module.py())?, - )?; Ok(()) } diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 9ef3487361c9..1ee0da3dc737 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -54,32 +54,32 @@ import qiskit._accelerate - # Globally define compiled submodules. The normal import mechanism will not find compiled submodules # in _accelerate because it relies on file paths, but PyO3 generates only one shared library file. # We manually define them on import so people can directly import qiskit._accelerate.* submodules # and not have to rely on attribute access. No action needed for top-level extension packages. -sys.modules["qiskit._accelerate.nlayout"] = qiskit._accelerate.nlayout -sys.modules["qiskit._accelerate.quantum_circuit"] = qiskit._accelerate.quantum_circuit -sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap -sys.modules["qiskit._accelerate.sabre"] = qiskit._accelerate.sabre -sys.modules["qiskit._accelerate.pauli_expval"] = qiskit._accelerate.pauli_expval +sys.modules["qiskit._accelerate.circuit"] = qiskit._accelerate.circuit +sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = ( + qiskit._accelerate.convert_2q_block_matrix +) sys.modules["qiskit._accelerate.dense_layout"] = qiskit._accelerate.dense_layout -sys.modules["qiskit._accelerate.sparse_pauli_op"] = qiskit._accelerate.sparse_pauli_op -sys.modules["qiskit._accelerate.results"] = qiskit._accelerate.results -sys.modules["qiskit._accelerate.optimize_1q_gates"] = qiskit._accelerate.optimize_1q_gates -sys.modules["qiskit._accelerate.sampled_exp_val"] = qiskit._accelerate.sampled_exp_val -sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout sys.modules["qiskit._accelerate.error_map"] = qiskit._accelerate.error_map sys.modules["qiskit._accelerate.euler_one_qubit_decomposer"] = ( qiskit._accelerate.euler_one_qubit_decomposer ) -sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = ( - qiskit._accelerate.convert_2q_block_matrix -) +sys.modules["qiskit._accelerate.nlayout"] = qiskit._accelerate.nlayout +sys.modules["qiskit._accelerate.optimize_1q_gates"] = qiskit._accelerate.optimize_1q_gates +sys.modules["qiskit._accelerate.pauli_expval"] = qiskit._accelerate.pauli_expval +sys.modules["qiskit._accelerate.qasm2"] = qiskit._accelerate.qasm2 +sys.modules["qiskit._accelerate.qasm3"] = qiskit._accelerate.qasm3 +sys.modules["qiskit._accelerate.results"] = qiskit._accelerate.results +sys.modules["qiskit._accelerate.sabre"] = qiskit._accelerate.sabre +sys.modules["qiskit._accelerate.sampled_exp_val"] = qiskit._accelerate.sampled_exp_val +sys.modules["qiskit._accelerate.sparse_pauli_op"] = qiskit._accelerate.sparse_pauli_op +sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose +sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout -# qiskit errors operator from qiskit.exceptions import QiskitError, MissingOptionalLibraryError # The main qiskit operators diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index c88e4c442e71..c6c95d27f924 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -24,7 +24,7 @@ import typing from typing import Collection, Iterable, FrozenSet, Tuple, Union, Optional, Sequence -from qiskit._accelerate.quantum_circuit import CircuitData +from qiskit._accelerate.circuit import CircuitData from qiskit.circuit.classical import expr from qiskit.circuit.classicalregister import Clbit, ClassicalRegister from qiskit.circuit.exceptions import CircuitError diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index f6c3404e38c4..3d1f5c77f44f 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -15,7 +15,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from qiskit._accelerate.quantum_circuit import CircuitData +from qiskit._accelerate.circuit import CircuitData from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit.parametertable import ParameterTable, ParameterView diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 56a283652f01..30afc47cb618 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -36,7 +36,7 @@ overload, ) import numpy as np -from qiskit._accelerate.quantum_circuit import CircuitData +from qiskit._accelerate.circuit import CircuitData from qiskit.exceptions import QiskitError from qiskit.utils.multiprocessing import is_main_process from qiskit.circuit.instruction import Instruction diff --git a/qiskit/circuit/quantumcircuitdata.py b/qiskit/circuit/quantumcircuitdata.py index 9808f461eea6..3e29f36c6bee 100644 --- a/qiskit/circuit/quantumcircuitdata.py +++ b/qiskit/circuit/quantumcircuitdata.py @@ -15,14 +15,14 @@ from collections.abc import MutableSequence -import qiskit._accelerate.quantum_circuit +import qiskit._accelerate.circuit from .exceptions import CircuitError from .instruction import Instruction from .operation import Operation -CircuitInstruction = qiskit._accelerate.quantum_circuit.CircuitInstruction +CircuitInstruction = qiskit._accelerate.circuit.CircuitInstruction class QuantumCircuitData(MutableSequence): diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index 485c210c0632..5a2f189c4102 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -526,12 +526,8 @@ def add_one(x): from pathlib import Path from typing import Iterable, Union, Optional, Literal -# Pylint can't handle the C-extension introspection of `_qasm2` because there's a re-import through -# to `qiskit.qasm2.exceptions`, and pylint ends up trying to import `_qasm2` twice, which PyO3 -# hates. If that gets fixed, this disable can be removed and `qiskit._qasm2` added to the allowed C -# extensions for loadings in the `pyproject.toml`. # pylint: disable=c-extension-no-member -from qiskit import _qasm2 +from qiskit._accelerate import qasm2 as _qasm2 from qiskit.circuit import QuantumCircuit from . import parse as _parse from .exceptions import QASM2Error, QASM2ParseError, QASM2ExportError diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py index 2bb4514dc2dc..5cb8137b5f0d 100644 --- a/qiskit/qasm2/parse.py +++ b/qiskit/qasm2/parse.py @@ -30,10 +30,7 @@ Reset, library as lib, ) - -# This is the same C-extension problems as described in the `__init__.py` disable near the -# `_qasm2` import. -from qiskit._qasm2 import ( # pylint: disable=no-name-in-module +from qiskit._accelerate.qasm2 import ( OpCode, UnaryOpCode, BinaryOpCode, diff --git a/qiskit/qasm3/__init__.py b/qiskit/qasm3/__init__.py index d1333addecb1..f3e11e8e2eb3 100644 --- a/qiskit/qasm3/__init__.py +++ b/qiskit/qasm3/__init__.py @@ -223,14 +223,51 @@ import functools import warnings -from qiskit import _qasm3 +from qiskit._accelerate import qasm3 as _qasm3 +from qiskit.circuit import library from qiskit.exceptions import ExperimentalWarning from qiskit.utils import optionals as _optionals from .experimental import ExperimentalFeatures from .exporter import Exporter from .exceptions import QASM3Error, QASM3ImporterError, QASM3ExporterError -from .._qasm3 import CustomGate, STDGATES_INC_GATES +from .._accelerate.qasm3 import CustomGate + + +STDGATES_INC_GATES = ( + CustomGate(library.PhaseGate, "p", 1, 1), + CustomGate(library.XGate, "x", 0, 1), + CustomGate(library.YGate, "y", 0, 1), + CustomGate(library.ZGate, "z", 0, 1), + CustomGate(library.HGate, "h", 0, 1), + CustomGate(library.SGate, "s", 0, 1), + CustomGate(library.SdgGate, "sdg", 0, 1), + CustomGate(library.TGate, "t", 0, 1), + CustomGate(library.TdgGate, "tdg", 0, 1), + CustomGate(library.SXGate, "sx", 0, 1), + CustomGate(library.RXGate, "rx", 1, 1), + CustomGate(library.RYGate, "ry", 1, 1), + CustomGate(library.RZGate, "rz", 1, 1), + CustomGate(library.CXGate, "cx", 0, 2), + CustomGate(library.CYGate, "cy", 0, 2), + CustomGate(library.CZGate, "cz", 0, 2), + CustomGate(library.CPhaseGate, "cp", 1, 2), + CustomGate(library.CRXGate, "crx", 1, 2), + CustomGate(library.CRYGate, "cry", 1, 2), + CustomGate(library.CRZGate, "crz", 1, 2), + CustomGate(library.CHGate, "ch", 0, 2), + CustomGate(library.SwapGate, "swap", 0, 2), + CustomGate(library.CCXGate, "ccx", 0, 3), + CustomGate(library.CSwapGate, "cswap", 0, 3), + CustomGate(library.CUGate, "cu", 4, 2), + CustomGate(library.CXGate, "CX", 0, 2), + CustomGate(library.PhaseGate, "phase", 1, 1), + CustomGate(library.CPhaseGate, "cphase", 1, 2), + CustomGate(library.IGate, "id", 0, 1), + CustomGate(library.U1Gate, "u1", 1, 1), + CustomGate(library.U2Gate, "u2", 2, 1), + CustomGate(library.U3Gate, "u3", 3, 1), +) def dumps(circuit, **kwargs) -> str: diff --git a/setup.py b/setup.py index 3c97ed6f0a05..9bb5b04ae6ec 100644 --- a/setup.py +++ b/setup.py @@ -34,22 +34,10 @@ rust_extensions=[ RustExtension( "qiskit._accelerate", - "crates/accelerate/Cargo.toml", + "crates/pyext/Cargo.toml", binding=Binding.PyO3, debug=rust_debug, - ), - RustExtension( - "qiskit._qasm2", - "crates/qasm2/Cargo.toml", - binding=Binding.PyO3, - debug=rust_debug, - ), - RustExtension( - "qiskit._qasm3", - "crates/qasm3/Cargo.toml", - binding=Binding.PyO3, - debug=rust_debug, - ), + ) ], options={"bdist_wheel": {"py_limited_api": "cp38"}}, ) diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index f2292b18a62b..73398e4316bb 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -13,7 +13,7 @@ """Test operations on circuit.data.""" import ddt -from qiskit._accelerate.quantum_circuit import CircuitData +from qiskit._accelerate.circuit import CircuitData from qiskit.circuit import ( ClassicalRegister, QuantumCircuit, From 3d676d2fd0d0ce0989bc4bcc4c8937d630b340ba Mon Sep 17 00:00:00 2001 From: Andy Maloney Date: Wed, 17 Apr 2024 15:14:51 -0500 Subject: [PATCH 002/179] Update gate dictionary in `two_local.py` (#12009) * Update gate dictionary in `two_local.py` This commit removes the hard coded gate dictionary with the one generated by the method `get_standard_gate_name_mapping`. Resolves #1202 * Release note * fix typo * Tweak release-note wording * Add explicit test --------- Co-authored-by: Jake Lishman --- qiskit/circuit/library/n_local/two_local.py | 62 +------------------ ...date-gate-dictionary-c0c017be67bb2f29.yaml | 7 +++ test/python/circuit/library/test_nlocal.py | 11 ++++ 3 files changed, 21 insertions(+), 59 deletions(-) create mode 100644 releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml diff --git a/qiskit/circuit/library/n_local/two_local.py b/qiskit/circuit/library/n_local/two_local.py index 066377300147..61b9e725cc6d 100644 --- a/qiskit/circuit/library/n_local/two_local.py +++ b/qiskit/circuit/library/n_local/two_local.py @@ -17,35 +17,10 @@ from collections.abc import Callable, Sequence from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit import Gate, Instruction, Parameter +from qiskit.circuit import Gate, Instruction from .n_local import NLocal -from ..standard_gates import ( - IGate, - XGate, - YGate, - ZGate, - RXGate, - RYGate, - RZGate, - HGate, - SGate, - SdgGate, - TGate, - TdgGate, - RXXGate, - RYYGate, - RZXGate, - RZZGate, - SwapGate, - CXGate, - CYGate, - CZGate, - CRXGate, - CRYGate, - CRZGate, - CHGate, -) +from ..standard_gates import get_standard_gate_name_mapping if typing.TYPE_CHECKING: import qiskit # pylint: disable=cyclic-import @@ -269,38 +244,7 @@ def _convert_to_block(self, layer: str | type | Gate | QuantumCircuit) -> Quantu if isinstance(layer, QuantumCircuit): return layer - # check the list of valid layers - # this could be a lot easier if the standard layers would have ``name`` and ``num_params`` - # as static types, which might be something they should have anyway - theta = Parameter("θ") - valid_layers = { - "ch": CHGate(), - "cx": CXGate(), - "cy": CYGate(), - "cz": CZGate(), - "crx": CRXGate(theta), - "cry": CRYGate(theta), - "crz": CRZGate(theta), - "h": HGate(), - "i": IGate(), - "id": IGate(), - "iden": IGate(), - "rx": RXGate(theta), - "rxx": RXXGate(theta), - "ry": RYGate(theta), - "ryy": RYYGate(theta), - "rz": RZGate(theta), - "rzx": RZXGate(theta), - "rzz": RZZGate(theta), - "s": SGate(), - "sdg": SdgGate(), - "swap": SwapGate(), - "x": XGate(), - "y": YGate(), - "z": ZGate(), - "t": TGate(), - "tdg": TdgGate(), - } + valid_layers = get_standard_gate_name_mapping() # try to exchange `layer` from a string to a gate instance if isinstance(layer, str): diff --git a/releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml b/releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml new file mode 100644 index 000000000000..b73d20886717 --- /dev/null +++ b/releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml @@ -0,0 +1,7 @@ +--- +features_circuits: + - | + All of the "standard gates" in the circuit library + (:mod:`qiskit.circuit.library`) can now be specified by string name for + the entangling operations in :class:`.TwoLocal` circuits, such as + :class:`.RealAmplitudes` and :class:`.EfficientSU2`. diff --git a/test/python/circuit/library/test_nlocal.py b/test/python/circuit/library/test_nlocal.py index fc003328b1ac..2e308af48ff4 100644 --- a/test/python/circuit/library/test_nlocal.py +++ b/test/python/circuit/library/test_nlocal.py @@ -37,6 +37,7 @@ RXXGate, RYYGate, CXGate, + SXGate, ) from qiskit.circuit.random.utils import random_circuit from qiskit.converters.circuit_to_dag import circuit_to_dag @@ -697,6 +698,16 @@ def test_ryrz_blocks(self): expected = [(-np.pi, np.pi)] * two.num_parameters np.testing.assert_almost_equal(two.parameter_bounds, expected) + def test_rzsx_blocks(self): + """Test that the EfficientSU2 circuit is instantiated correctly.""" + two = EfficientSU2(3, ["rz", "sx", "rz", "sx", "rz"]) + expected = [[RZGate], [SXGate], [RZGate], [SXGate], [RZGate]] + actual = [ + [instruction.operation.base_class for instruction in block] + for block in two.rotation_blocks + ] + self.assertEqual(actual, expected) + def test_ryrz_circuit(self): """Test an EfficientSU2 circuit.""" num_qubits = 3 From b204015ddb7209c885d09d8e8d27c7c195dd5eee Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Thu, 18 Apr 2024 04:32:24 -0400 Subject: [PATCH 003/179] updates for removing use-maxsplit-arg (#12199) --- pyproject.toml | 1 - test/python/visualization/test_circuit_drawer.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8cd6360f97c9..fcde75f371c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -241,7 +241,6 @@ disable = [ "use-dict-literal", "use-list-literal", "use-implicit-booleaness-not-comparison", - "use-maxsplit-arg", ] enable = [ diff --git a/test/python/visualization/test_circuit_drawer.py b/test/python/visualization/test_circuit_drawer.py index 0c16a57133d5..f02f1ad11431 100644 --- a/test/python/visualization/test_circuit_drawer.py +++ b/test/python/visualization/test_circuit_drawer.py @@ -136,7 +136,7 @@ def test_latex_output_file_correct_format(self): if filename.endswith("jpg"): self.assertIn(im.format.lower(), "jpeg") else: - self.assertIn(im.format.lower(), filename.split(".")[-1]) + self.assertIn(im.format.lower(), filename.rsplit(".", maxsplit=1)[-1]) os.remove(filename) def test_wire_order(self): From 9ede1b6ace329b399fbf226b839df8190f1b4115 Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Thu, 18 Apr 2024 05:21:24 -0400 Subject: [PATCH 004/179] Updates for removing non-ascii-name (#12198) * updates for removing non-ascii-name * fix missed lint issue --- pyproject.toml | 1 - .../test_optimize_1q_decomposition.py | 66 +++++++++---------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fcde75f371c9..f2439bd93be4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,7 +225,6 @@ disable = [ "nested-min-max", "no-member", "no-value-for-parameter", - "non-ascii-name", "not-context-manager", "superfluous-parens", "unknown-option-value", diff --git a/test/python/transpiler/test_optimize_1q_decomposition.py b/test/python/transpiler/test_optimize_1q_decomposition.py index f395fc8a1d94..06aab474d611 100644 --- a/test/python/transpiler/test_optimize_1q_decomposition.py +++ b/test/python/transpiler/test_optimize_1q_decomposition.py @@ -40,31 +40,31 @@ from test import QiskitTestCase # pylint: disable=wrong-import-order -θ = Parameter("θ") -ϕ = Parameter("ϕ") -λ = Parameter("λ") +theta = Parameter("θ") +phi = Parameter("ϕ") +lambda_ = Parameter("λ") # a typical target where u1 is cheaper than u2 is cheaper than u3 u1_props = {(0,): InstructionProperties(error=0)} u2_props = {(0,): InstructionProperties(error=1e-4)} u3_props = {(0,): InstructionProperties(error=2e-4)} target_u1_u2_u3 = Target() -target_u1_u2_u3.add_instruction(U1Gate(θ), u1_props, name="u1") -target_u1_u2_u3.add_instruction(U2Gate(θ, ϕ), u2_props, name="u2") -target_u1_u2_u3.add_instruction(U3Gate(θ, ϕ, λ), u3_props, name="u3") +target_u1_u2_u3.add_instruction(U1Gate(theta), u1_props, name="u1") +target_u1_u2_u3.add_instruction(U2Gate(theta, phi), u2_props, name="u2") +target_u1_u2_u3.add_instruction(U3Gate(theta, phi, lambda_), u3_props, name="u3") # a typical target where continuous rz and rx are available; rz is cheaper rz_props = {(0,): InstructionProperties(duration=0, error=0)} rx_props = {(0,): InstructionProperties(duration=0.5e-8, error=0.00025)} target_rz_rx = Target() -target_rz_rx.add_instruction(RZGate(θ), rz_props, name="rz") -target_rz_rx.add_instruction(RXGate(θ), rx_props, name="rx") +target_rz_rx.add_instruction(RZGate(theta), rz_props, name="rz") +target_rz_rx.add_instruction(RXGate(theta), rx_props, name="rx") # a typical target where continuous rz, and discrete sx are available; rz is cheaper rz_props = {(0,): InstructionProperties(duration=0, error=0)} sx_props = {(0,): InstructionProperties(duration=0.5e-8, error=0.00025)} target_rz_sx = Target() -target_rz_sx.add_instruction(RZGate(θ), rz_props, name="rz") +target_rz_sx.add_instruction(RZGate(theta), rz_props, name="rz") target_rz_sx.add_instruction(SXGate(), sx_props, name="sx") # a target with overcomplete basis, rz is cheaper than ry is cheaper than u @@ -72,9 +72,9 @@ ry_props = {(0,): InstructionProperties(duration=0.5e-8, error=0.0002)} u_props = {(0,): InstructionProperties(duration=0.9e-8, error=0.0005)} target_rz_ry_u = Target() -target_rz_ry_u.add_instruction(RZGate(θ), rz_props, name="rz") -target_rz_ry_u.add_instruction(RYGate(θ), ry_props, name="ry") -target_rz_ry_u.add_instruction(UGate(θ, ϕ, λ), u_props, name="u") +target_rz_ry_u.add_instruction(RZGate(theta), rz_props, name="rz") +target_rz_ry_u.add_instruction(RYGate(theta), ry_props, name="ry") +target_rz_ry_u.add_instruction(UGate(theta, phi, lambda_), u_props, name="u") # a target with hadamard and phase, we don't yet have an explicit decomposer # but we can at least recognize circuits that are native for it @@ -82,7 +82,7 @@ p_props = {(0,): InstructionProperties(duration=0, error=0)} target_h_p = Target() target_h_p.add_instruction(HGate(), h_props, name="h") -target_h_p.add_instruction(PhaseGate(θ), p_props, name="p") +target_h_p.add_instruction(PhaseGate(theta), p_props, name="p") # a target with rz, ry, and u. Error are not specified so we should prefer # shorter decompositions. @@ -90,9 +90,9 @@ ry_props = {(0,): None} u_props = {(0,): None} target_rz_ry_u_noerror = Target() -target_rz_ry_u_noerror.add_instruction(RZGate(θ), rz_props, name="rz") -target_rz_ry_u_noerror.add_instruction(RYGate(θ), ry_props, name="ry") -target_rz_ry_u_noerror.add_instruction(UGate(θ, ϕ, λ), u_props, name="u") +target_rz_ry_u_noerror.add_instruction(RZGate(theta), rz_props, name="rz") +target_rz_ry_u_noerror.add_instruction(RYGate(theta), ry_props, name="ry") +target_rz_ry_u_noerror.add_instruction(UGate(theta, phi, lambda_), u_props, name="u") @ddt.ddt @@ -274,11 +274,11 @@ def test_single_parameterized_circuit(self, basis): """Parameters should be treated as opaque gates.""" qr = QuantumRegister(1) qc = QuantumCircuit(qr) - theta = Parameter("theta") + theta_p = Parameter("theta") qc.p(0.3, qr) qc.p(0.4, qr) - qc.p(theta, qr) + qc.p(theta_p, qr) qc.p(0.1, qr) qc.p(0.2, qr) @@ -287,8 +287,8 @@ def test_single_parameterized_circuit(self, basis): result = passmanager.run(qc) self.assertTrue( - Operator(qc.assign_parameters({theta: 3.14})).equiv( - Operator(result.assign_parameters({theta: 3.14})) + Operator(qc.assign_parameters({theta_p: 3.14})).equiv( + Operator(result.assign_parameters({theta_p: 3.14})) ) ) @@ -308,14 +308,14 @@ def test_parameterized_circuits(self, basis): """Parameters should be treated as opaque gates.""" qr = QuantumRegister(1) qc = QuantumCircuit(qr) - theta = Parameter("theta") + theta_p = Parameter("theta") qc.p(0.3, qr) qc.p(0.4, qr) - qc.p(theta, qr) + qc.p(theta_p, qr) qc.p(0.1, qr) qc.p(0.2, qr) - qc.p(theta, qr) + qc.p(theta_p, qr) qc.p(0.3, qr) qc.p(0.2, qr) @@ -324,8 +324,8 @@ def test_parameterized_circuits(self, basis): result = passmanager.run(qc) self.assertTrue( - Operator(qc.assign_parameters({theta: 3.14})).equiv( - Operator(result.assign_parameters({theta: 3.14})) + Operator(qc.assign_parameters({theta_p: 3.14})).equiv( + Operator(result.assign_parameters({theta_p: 3.14})) ) ) @@ -345,15 +345,15 @@ def test_parameterized_expressions_in_circuits(self, basis): """Expressions of Parameters should be treated as opaque gates.""" qr = QuantumRegister(1) qc = QuantumCircuit(qr) - theta = Parameter("theta") - phi = Parameter("phi") + theta_p = Parameter("theta") + phi_p = Parameter("phi") - sum_ = theta + phi - product_ = theta * phi + sum_ = theta_p + phi_p + product_ = theta_p * phi_p qc.p(0.3, qr) qc.p(0.4, qr) - qc.p(theta, qr) - qc.p(phi, qr) + qc.p(theta_p, qr) + qc.p(phi_p, qr) qc.p(sum_, qr) qc.p(product_, qr) qc.p(0.3, qr) @@ -364,8 +364,8 @@ def test_parameterized_expressions_in_circuits(self, basis): result = passmanager.run(qc) self.assertTrue( - Operator(qc.assign_parameters({theta: 3.14, phi: 10})).equiv( - Operator(result.assign_parameters({theta: 3.14, phi: 10})) + Operator(qc.assign_parameters({theta_p: 3.14, phi_p: 10})).equiv( + Operator(result.assign_parameters({theta_p: 3.14, phi_p: 10})) ) ) From 437837e378c31cbbb4a6a5579584534671bd23a5 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 18 Apr 2024 13:52:22 +0100 Subject: [PATCH 005/179] Add build requirements to `requirements-dev.txt` (#12190) We previously have not added the build-only requirements `setuptools` and `setuptools-rust` to `requirements-dev.txt` because the majority of contributors would not to build the Rust components outside of the regular build isolation provided by `pip` or other PEP-517 compatible builders. Since we are accelerating our use of Rust, and it's more likely that more users will need to touch the Rust components, this adds the build requirements to the developer environment, so it's easier for people to do our recommended python setup.py build_rust --inplace --release for the cases that they want to test against optimised versions of the Rust extensions while still maintaining a Python editable installation. --- requirements-dev.txt | 4 ++++ tox.ini | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 62e29f6dbfa8..c75237e77edd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,6 +5,10 @@ # never become actual package requirements, but still try to be as relaxed as # possible so it's easy to develop multiple packages from the same venv. +# Build Rust directly +setuptools +setuptools-rust + # Style black[jupyter]~=24.1 diff --git a/tox.ini b/tox.ini index ef1e1a23ef60..3ee544538a09 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,6 @@ passenv = QISKIT_IN_PARALLEL QISKIT_DOCS_GITHUB_BRANCH_NAME deps = - setuptools_rust # This is work around for the bug of tox 3 (see #8606 for more details.) -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-dev.txt commands = From d10f9a0fe1f73cde5b66e49af880a6f7816f131f Mon Sep 17 00:00:00 2001 From: aeddins-ibm <60495383+aeddins-ibm@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:52:53 -0400 Subject: [PATCH 006/179] Support ECR gates in `Pauli.evolve(QuantumCircuit)` (#12095) * add _evolve_ecr() - Adds support for Pauli (and related classes) to evolve through ECR gates encountered in a quantum circuit. - Also moved the dicts of special-case gates (`basis_1q`, `basis_2q`, `non-clifford`) outside the subroutine definition. They are now just after the `_evolve_*()` functions they reference. * fix pauli.evolve bug for certain circuit names - Should fix qiskit issue #12093 - Bug happened after converting circuit to instruction, which AFAICT was not necessary. Now if input is a QuantumCircuit, that part of the code is bypassed. - Removed creation of a look-up dict of bit locations, since `QuantumCircuit.find_bit` already provides one. * add ECRGate to `evolve()` tests * mark gate-dicts as private * add test evolving by circuit named 'cx' Test showing issue #12093 is solved. * add release note for pauli-evolve fixes * Update test_pauli_list.py --- .../operators/symplectic/base_pauli.py | 129 ++++++++++-------- .../fix-pauli-evolve-ecr-and-name-bugs.yaml | 6 + .../operators/symplectic/test_pauli.py | 15 +- .../operators/symplectic/test_pauli_list.py | 4 + 4 files changed, 94 insertions(+), 60 deletions(-) create mode 100644 releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 28bb647d0364..e43eca4aff21 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -541,75 +541,50 @@ def _append_circuit(self, circuit, qargs=None): if qargs is None: qargs = list(range(self.num_qubits)) - if isinstance(circuit, QuantumCircuit): - gate = circuit.to_instruction() - else: + if not isinstance(circuit, QuantumCircuit): gate = circuit - # Basis Clifford Gates - basis_1q = { - "i": _evolve_i, - "id": _evolve_i, - "iden": _evolve_i, - "x": _evolve_x, - "y": _evolve_y, - "z": _evolve_z, - "h": _evolve_h, - "s": _evolve_s, - "sdg": _evolve_sdg, - "sinv": _evolve_sdg, - } - basis_2q = {"cx": _evolve_cx, "cz": _evolve_cz, "cy": _evolve_cy, "swap": _evolve_swap} - - # Non-Clifford gates - non_clifford = ["t", "tdg", "ccx", "ccz"] - - if isinstance(gate, str): - # Check if gate is a valid Clifford basis gate string - if gate not in basis_1q and gate not in basis_2q: - raise QiskitError(f"Invalid Clifford gate name string {gate}") - name = gate - else: - # Assume gate is an Instruction - name = gate.name - - # Apply gate if it is a Clifford basis gate - if name in non_clifford: - raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}") - if name in basis_1q: - if len(qargs) != 1: - raise QiskitError("Invalid qubits for 1-qubit gate.") - return basis_1q[name](self, qargs[0]) - if name in basis_2q: - if len(qargs) != 2: - raise QiskitError("Invalid qubits for 2-qubit gate.") - return basis_2q[name](self, qargs[0], qargs[1]) - - # If not a Clifford basis gate we try to unroll the gate and - # raise an exception if unrolling reaches a non-Clifford gate. - if gate.definition is None: - raise QiskitError(f"Cannot apply Instruction: {gate.name}") - if not isinstance(gate.definition, QuantumCircuit): - raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - gate.name, type(gate.definition) + if isinstance(gate, str): + # Check if gate is a valid Clifford basis gate string + if gate not in _basis_1q and gate not in _basis_2q: + raise QiskitError(f"Invalid Clifford gate name string {gate}") + name = gate + else: + # Assume gate is an Instruction + name = gate.name + + # Apply gate if it is a Clifford basis gate + if name in _non_clifford: + raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}") + if name in _basis_1q: + if len(qargs) != 1: + raise QiskitError("Invalid qubits for 1-qubit gate.") + return _basis_1q[name](self, qargs[0]) + if name in _basis_2q: + if len(qargs) != 2: + raise QiskitError("Invalid qubits for 2-qubit gate.") + return _basis_2q[name](self, qargs[0], qargs[1]) + + # If not a Clifford basis gate we try to unroll the gate and + # raise an exception if unrolling reaches a non-Clifford gate. + if gate.definition is None: + raise QiskitError(f"Cannot apply Instruction: {gate.name}") + if not isinstance(gate.definition, QuantumCircuit): + raise QiskitError( + "{} instruction definition is {}; expected QuantumCircuit".format( + gate.name, type(gate.definition) + ) ) - ) - flat_instr = gate.definition - bit_indices = { - bit: index - for bits in [flat_instr.qubits, flat_instr.clbits] - for index, bit in enumerate(bits) - } + circuit = gate.definition - for instruction in flat_instr: + for instruction in circuit: if instruction.clbits: raise QiskitError( f"Cannot apply Instruction with classical bits: {instruction.operation.name}" ) # Get the integer position of the flat register - new_qubits = [qargs[bit_indices[tup]] for tup in instruction.qubits] + new_qubits = [qargs[circuit.find_bit(qb)[0]] for qb in instruction.qubits] self._append_circuit(instruction.operation, new_qubits) # Since the individual gate evolution functions don't take mod @@ -715,6 +690,42 @@ def _evolve_swap(base_pauli, q1, q2): return base_pauli +def _evolve_ecr(base_pauli, q1, q2): + """Update P -> ECR.P.ECR""" + base_pauli = _evolve_s(base_pauli, q1) + base_pauli = _evolve_h(base_pauli, q2) + base_pauli = _evolve_s(base_pauli, q2) + base_pauli = _evolve_h(base_pauli, q2) + base_pauli = _evolve_cx(base_pauli, q1, q2) + base_pauli = _evolve_x(base_pauli, q1) + return base_pauli + + def _count_y(x, z, dtype=None): """Count the number of I Paulis""" return (x & z).sum(axis=1, dtype=dtype) + + +# Basis Clifford Gates +_basis_1q = { + "i": _evolve_i, + "id": _evolve_i, + "iden": _evolve_i, + "x": _evolve_x, + "y": _evolve_y, + "z": _evolve_z, + "h": _evolve_h, + "s": _evolve_s, + "sdg": _evolve_sdg, + "sinv": _evolve_sdg, +} +_basis_2q = { + "cx": _evolve_cx, + "cz": _evolve_cz, + "cy": _evolve_cy, + "swap": _evolve_swap, + "ecr": _evolve_ecr, +} + +# Non-Clifford gates +_non_clifford = ["t", "tdg", "ccx", "ccz"] diff --git a/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml new file mode 100644 index 000000000000..143dfadf31e2 --- /dev/null +++ b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + :meth:`.Pauli.evolve` now correctly handles quantum circuits containing ECR gates. Formerly they were not recognized as Clifford gates, and an error was raised. + - | + Fixed a bug in :meth:`.Pauli.evolve` where evolving by a circuit with a name matching certain Clifford gates ('cx', 'cz', etc) would evolve the Pauli according to the name of the circuit, not by the contents of the circuit. This bug occurred only with the non-default option ``frame='s'``. diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index c760407ad3fa..875dd9237810 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -36,6 +36,7 @@ CZGate, CYGate, SwapGate, + ECRGate, EfficientSU2, ) from qiskit.circuit.library.generalized_gates import PauliGate @@ -410,7 +411,11 @@ def test_evolve_clifford1(self, gate, label): self.assertEqual(value, value_h) self.assertEqual(value_inv, value_s) - @data(*it.product((CXGate(), CYGate(), CZGate(), SwapGate()), pauli_group_labels(2, False))) + @data( + *it.product( + (CXGate(), CYGate(), CZGate(), SwapGate(), ECRGate()), pauli_group_labels(2, False) + ) + ) @unpack def test_evolve_clifford2(self, gate, label): """Test evolve method for 2-qubit Clifford gates.""" @@ -439,6 +444,7 @@ def test_evolve_clifford2(self, gate, label): CYGate(), CZGate(), SwapGate(), + ECRGate(), ), [int, np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64], ) @@ -468,6 +474,13 @@ def test_evolve_clifford_qargs(self): self.assertEqual(value, value_h) self.assertEqual(value_inv, value_s) + @data("s", "h") + def test_evolve_with_misleading_name(self, frame): + """Test evolve by circuit contents, not by name (fixed bug).""" + circ = QuantumCircuit(2, name="cx") + p = Pauli("IX") + self.assertEqual(p, p.evolve(circ, frame=frame)) + def test_barrier_delay_sim(self): """Test barrier and delay instructions can be simulated""" target_circ = QuantumCircuit(2) diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index 7ab6feaefe98..0ef7079f461a 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -33,6 +33,7 @@ XGate, YGate, ZGate, + ECRGate, ) from qiskit.quantum_info.operators import ( Clifford, @@ -1997,10 +1998,12 @@ def test_evolve_clifford1(self, gate): CYGate(), CZGate(), SwapGate(), + ECRGate(), Clifford(CXGate()), Clifford(CYGate()), Clifford(CZGate()), Clifford(SwapGate()), + Clifford(ECRGate()), ) ) def test_evolve_clifford2(self, gate): @@ -2033,6 +2036,7 @@ def test_phase_dtype_evolve_clifford(self): CYGate(), CZGate(), SwapGate(), + ECRGate(), ) dtypes = [ int, From ff7ba3f1a61b6e474c00055abb62fd99dd393a99 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 18 Apr 2024 18:01:37 -0400 Subject: [PATCH 007/179] Document generate_preset_pass_manager supports list for initial_layout (#12214) This commit updates the documentation for the generate_preset_pass_manager() function to clearly indicate that the function will accept a integer list for the initial_layout field. This already was supported but it wasn't documented. As it's now a documented part of the API a unittest is added to ensure we don't regress this functionality in the future. Fixes #11690 --- .../preset_passmanagers/__init__.py | 2 +- .../transpiler/test_preset_passmanagers.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index d8a7fc9b5158..8d653ed3a1a9 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -136,7 +136,7 @@ def generate_preset_pass_manager( instruction_durations (InstructionDurations): Dictionary of duration (in dt) for each instruction. timing_constraints (TimingConstraints): Hardware time alignment restrictions. - initial_layout (Layout): Initial position of virtual qubits on + initial_layout (Layout | List[int]): Initial position of virtual qubits on physical qubits. layout_method (str): The :class:`~.Pass` to use for choosing initial qubit placement. Valid choices are ``'trivial'``, ``'dense'``, diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 0382c83ab754..ae1837bf111b 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1436,6 +1436,38 @@ def test_generate_preset_pass_manager_with_list_coupling_map(self): # Ensure the DAGs from both methods are identical self.assertEqual(transpiled_circuit_list, transpiled_circuit_object) + @data(0, 1, 2, 3) + def test_generate_preset_pass_manager_with_list_initial_layout(self, optimization_level): + """Test that generate_preset_pass_manager can handle list based initial layouts.""" + coupling_map_list = [[0, 1]] + + # Circuit that doesn't fit in the coupling map + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 0) + qc.measure_all() + + pm_list = generate_preset_pass_manager( + optimization_level=optimization_level, + coupling_map=coupling_map_list, + basis_gates=["u", "cx"], + seed_transpiler=42, + initial_layout=[1, 0], + ) + pm_object = generate_preset_pass_manager( + optimization_level=optimization_level, + coupling_map=coupling_map_list, + basis_gates=["u", "cx"], + seed_transpiler=42, + initial_layout=Layout.from_intlist([1, 0], *qc.qregs), + ) + tqc_list = pm_list.run(qc) + tqc_obj = pm_list.run(qc) + self.assertIsInstance(pm_list, PassManager) + self.assertIsInstance(pm_object, PassManager) + self.assertEqual(tqc_list, tqc_obj) + @ddt class TestIntegrationControlFlow(QiskitTestCase): From 5dbc0c04cec1327977be788b50f554c3c7cda344 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 19 Apr 2024 03:56:00 +0100 Subject: [PATCH 008/179] Fix mistaken QPY custom-instructions documentation (#12216) `num_custom_gates` is not actually part of the `CIRCUIT_HEADER` structs in any version of QPY. The number of custom-instruction objects is instead stored as a `uint64_t` inline in the `CUSTOM_DEFINITIONS` part of the file, so separately to the rest of the header. --- qiskit/qpy/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index c04ca409d093..1274ff110be6 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -1101,7 +1101,6 @@ uint64_t metadata_size; uint32_t num_registers; uint64_t num_instructions; - uint64_t num_custom_gates; } This is immediately followed by ``name_size`` bytes of utf8 data for the name @@ -1134,7 +1133,6 @@ uint64_t metadata_size; uint32_t num_registers; uint64_t num_instructions; - uint64_t num_custom_gates; } This is immediately followed by ``name_size`` bytes of utf8 data for the name From 5fb343f5f2faf5578dc0afcd6a3393fbaa0e0c17 Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Thu, 18 Apr 2024 22:59:58 -0400 Subject: [PATCH 009/179] removing lint rules that are unused (#12217) --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f2439bd93be4..b48b5c6c28e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -227,18 +227,14 @@ disable = [ "no-value-for-parameter", "not-context-manager", "superfluous-parens", - "unknown-option-value", "unexpected-keyword-arg", "unnecessary-dict-index-lookup", - "unnecessary-direct-lambda-call", "unnecessary-dunder-call", - "unnecessary-ellipsis", "unnecessary-lambda-assignment", "unnecessary-list-index-lookup", "unspecified-encoding", "unsupported-assignment-operation", "use-dict-literal", - "use-list-literal", "use-implicit-booleaness-not-comparison", ] From 71753689ec7d72aa5ed3f513069d6644cb977277 Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Sat, 20 Apr 2024 21:09:01 -0400 Subject: [PATCH 010/179] removing unnecessary-list-index-lookup from lint exclusions and updates (#12285) --- pyproject.toml | 1 - qiskit/transpiler/passes/routing/stochastic_swap.py | 2 +- test/python/compiler/test_transpiler.py | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b48b5c6c28e5..0d99f9256df3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,7 +231,6 @@ disable = [ "unnecessary-dict-index-lookup", "unnecessary-dunder-call", "unnecessary-lambda-assignment", - "unnecessary-list-index-lookup", "unspecified-encoding", "unsupported-assignment-operation", "use-dict-literal", diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index 105ed9d23a5a..3b80bf7b31af 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -323,7 +323,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # Update the DAG if not self.fake_run: self._layer_update( - dagcircuit_output, layerlist[i], best_layout, best_depth, best_circuit + dagcircuit_output, layer, best_layout, best_depth, best_circuit ) continue diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 6eb54ddc4be9..d4e19da541a0 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -294,10 +294,10 @@ def test_transpile_qft_grid(self): qr = QuantumRegister(8) circuit = QuantumCircuit(qr) - for i, _ in enumerate(qr): + for i, q in enumerate(qr): for j in range(i): - circuit.cp(math.pi / float(2 ** (i - j)), qr[i], qr[j]) - circuit.h(qr[i]) + circuit.cp(math.pi / float(2 ** (i - j)), q, qr[j]) + circuit.h(q) new_circuit = transpile( circuit, basis_gates=basis_gates, coupling_map=MELBOURNE_CMAP, seed_transpiler=42 From 497603bd7f767fe5527eca946edafb509475c3f3 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 22 Apr 2024 05:17:07 -0400 Subject: [PATCH 011/179] Avoid intermediate DAGs in Optimize1qGatesDecomposition (#12176) This commit removes the use of intermediate DAGCircuit objects and calling substitute_node_with_dag() in the Optimize1qGatesDecomposition pass. Since everything is 1q it's very easy to just directly insert the nodes on the DAG prior to the run and then remove the old nodes. This avoids a lot of extra operations and overhead to create a second dagcircuit for each identified run and then substiting that dag in place of the run. --- .../passes/optimization/optimize_1q_decomposition.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 0cc8fee34aee..3f8d07839c0f 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -34,6 +34,7 @@ ) from qiskit.circuit import Qubit from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagnode import DAGOpNode logger = logging.getLogger(__name__) @@ -213,10 +214,15 @@ def run(self, dag): if best_circuit_sequence is not None and self._substitution_checks( dag, run, best_circuit_sequence, basis, qubit ): - new_dag = self._gate_sequence_to_dag(best_circuit_sequence) - dag.substitute_node_with_dag(run[0], new_dag) + for gate_name, angles in best_circuit_sequence: + op = NAME_MAP[gate_name](*angles) + node = DAGOpNode(NAME_MAP[gate_name](*angles), run[0].qargs, dag=dag) + node._node_id = dag._multi_graph.add_node(node) + dag._increment_op(op) + dag._multi_graph.insert_node_on_in_edges(node._node_id, run[0]._node_id) + dag.global_phase += best_circuit_sequence.global_phase # Delete the other nodes in the run - for current_node in run[1:]: + for current_node in run: dag.remove_op_node(current_node) return dag From cedf0300152da25c2fef3c4456c6aa320c354fb3 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Mon, 22 Apr 2024 13:55:11 +0300 Subject: [PATCH 012/179] Expose plugin options (#12107) * fix docstring * import * exposing additional plugin arguments * tests * lint * release notes --- qiskit/synthesis/linear/cnot_synth.py | 4 +- .../synthesis/linear/linear_circuits_utils.py | 2 +- .../passes/synthesis/high_level_synthesis.py | 84 +++++++++++- ...inear-plugin-options-b8a0ffe70dfe1676.yaml | 21 +++ .../transpiler/test_high_level_synthesis.py | 129 ++++++++++++++++++ 5 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py index 4d3793c38724..5063577ed653 100644 --- a/qiskit/synthesis/linear/cnot_synth.py +++ b/qiskit/synthesis/linear/cnot_synth.py @@ -37,9 +37,7 @@ def synth_cnot_count_full_pmh( Args: state: :math:`n \\times n` boolean invertible matrix, describing the state of the input circuit - section_size: The size of each section, used in the - Patel–Markov–Hayes algorithm [1]. ``section_size`` must be a factor of the number - of qubits. + section_size: The size of each section in the Patel–Markov–Hayes algorithm [1]. Returns: QuantumCircuit: a CX-only circuit implementing the linear transformation. diff --git a/qiskit/synthesis/linear/linear_circuits_utils.py b/qiskit/synthesis/linear/linear_circuits_utils.py index ad6a6601b632..d9ddfe0d081c 100644 --- a/qiskit/synthesis/linear/linear_circuits_utils.py +++ b/qiskit/synthesis/linear/linear_circuits_utils.py @@ -15,7 +15,7 @@ import copy from typing import Callable import numpy as np -from qiskit import QuantumCircuit +from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.circuit.exceptions import CircuitError from . import calc_inverse_matrix, check_invertible_binary_matrix diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 345843284fa3..3d3e2a6851af 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -135,6 +135,7 @@ from typing import Optional, Union, List, Tuple +import numpy as np import rustworkx as rx from qiskit.circuit.operation import Operation @@ -142,6 +143,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit import ControlFlowOp, ControlledGate, EquivalenceLibrary +from qiskit.circuit.library import LinearFunction from qiskit.transpiler.passes.utils import control_flow from qiskit.transpiler.target import Target from qiskit.transpiler.coupling import CouplingMap @@ -163,7 +165,12 @@ synth_clifford_ag, synth_clifford_bm, ) -from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms +from qiskit.synthesis.linear import ( + synth_cnot_count_full_pmh, + synth_cnot_depth_line_kms, + calc_inverse_matrix, +) +from qiskit.synthesis.linear.linear_circuits_utils import transpose_cx_circ from qiskit.synthesis.permutation import ( synth_permutation_basic, synth_permutation_acg, @@ -720,11 +727,43 @@ class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin): This plugin name is :``linear_function.kms`` which can be used as the key on an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + The plugin supports the following plugin-specific options: + + * use_inverted: Indicates whether to run the algorithm on the inverse matrix + and to invert the synthesized circuit. + In certain cases this provides a better decomposition then the direct approach. + * use_transposed: Indicates whether to run the algorithm on the transposed matrix + and to invert the order oF CX gates in the synthesized circuit. + In certain cases this provides a better decomposition than the direct approach. + """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): """Run synthesis for the given LinearFunction.""" - decomposition = synth_cnot_depth_line_kms(high_level_object.linear) + + if not isinstance(high_level_object, LinearFunction): + raise TranspilerError( + "PMHSynthesisLinearFunction only accepts objects of type LinearFunction" + ) + + use_inverted = options.get("use_inverted", False) + use_transposed = options.get("use_transposed", False) + + mat = high_level_object.linear.astype(int) + + if use_transposed: + mat = np.transpose(mat) + if use_inverted: + mat = calc_inverse_matrix(mat) + + decomposition = synth_cnot_depth_line_kms(mat) + + if use_transposed: + decomposition = transpose_cx_circ(decomposition) + if use_inverted: + decomposition = decomposition.inverse() + return decomposition @@ -733,11 +772,50 @@ class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin): This plugin name is :``linear_function.pmh`` which can be used as the key on an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. + + The plugin supports the following plugin-specific options: + + * section size: The size of each section used in the Patel–Markov–Hayes algorithm [1]. + * use_inverted: Indicates whether to run the algorithm on the inverse matrix + and to invert the synthesized circuit. + In certain cases this provides a better decomposition then the direct approach. + * use_transposed: Indicates whether to run the algorithm on the transposed matrix + and to invert the order oF CX gates in the synthesized circuit. + In certain cases this provides a better decomposition than the direct approach. + + References: + 1. Patel, Ketan N., Igor L. Markov, and John P. Hayes, + *Optimal synthesis of linear reversible circuits*, + Quantum Information & Computation 8.3 (2008): 282-294. + `arXiv:quant-ph/0302002 [quant-ph] `_ """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): """Run synthesis for the given LinearFunction.""" - decomposition = synth_cnot_count_full_pmh(high_level_object.linear) + + if not isinstance(high_level_object, LinearFunction): + raise TranspilerError( + "PMHSynthesisLinearFunction only accepts objects of type LinearFunction" + ) + + section_size = options.get("section_size", 2) + use_inverted = options.get("use_inverted", False) + use_transposed = options.get("use_transposed", False) + + mat = high_level_object.linear.astype(int) + + if use_transposed: + mat = np.transpose(mat) + if use_inverted: + mat = calc_inverse_matrix(mat) + + decomposition = synth_cnot_count_full_pmh(mat, section_size=section_size) + + if use_transposed: + decomposition = transpose_cx_circ(decomposition) + if use_inverted: + decomposition = decomposition.inverse() + return decomposition diff --git a/releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml b/releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml new file mode 100644 index 000000000000..efd0453ca464 --- /dev/null +++ b/releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml @@ -0,0 +1,21 @@ +--- +features: + - | + The :class:`.KMSSynthesisLinearFunction` plugin for synthesizing + :class:`~qiskit.circuit.library.LinearFunction` objects now accepts + two additional options ``use_inverted`` and ``use_transposed``. + These option modify the matrix on which the underlying synthesis algorithm runs + by possibly inverting and/or transposing it, and then suitably adjust + the synthesized circuit. By varying these options, we generally get different + synthesized circuits, and in cases may obtain better results than for + their default values. + - | + The :class:`.PMHSynthesisLinearFunction` plugin for synthesizing + :class:`~qiskit.circuit.library.LinearFunction` objects now accepts + several additional options. The option ``section_size`` is passed to the underlying + synthesis method. The options ``use_inverted`` and ``use_transposed`` + modify the matrix on which the underlying synthesis algorithm runs + by possibly inverting and/or transposing it, and then suitably adjust + the synthesized circuit. By varying these options, we generally get different + synthesized circuits, and in cases may obtain better results than for + their default values. diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index b4f997525ad7..5ab78af8f581 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -41,6 +41,7 @@ ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford +from qiskit.synthesis.linear import random_invertible_binary_matrix from qiskit.transpiler.passes.synthesis.plugin import ( HighLevelSynthesisPlugin, HighLevelSynthesisPluginManager, @@ -508,6 +509,134 @@ def test_qubits_get_passed_to_plugins(self): pm_use_qubits_true.run(qc) +class TestPMHSynthesisLinearFunctionPlugin(QiskitTestCase): + """Tests for the PMHSynthesisLinearFunction plugin for synthesizing linear functions.""" + + @staticmethod + def construct_linear_circuit(num_qubits: int): + """Construct linear circuit.""" + qc = QuantumCircuit(num_qubits) + for i in range(1, num_qubits): + qc.cx(i - 1, i) + return qc + + def test_section_size(self): + """Test that the plugin takes the section size argument into account.""" + + mat = random_invertible_binary_matrix(7, seed=1234) + qc = QuantumCircuit(7) + qc.append(LinearFunction(mat), [0, 1, 2, 3, 4, 5, 6]) + + with self.subTest("section_size_1"): + hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 1})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 22) + self.assertEqual(qct.depth(), 20) + + with self.subTest("section_size_2"): + hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 2})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 23) + self.assertEqual(qct.depth(), 19) + + with self.subTest("section_size_3"): + hls_config = HLSConfig(linear_function=[("pmh", {"section_size": 3})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 23) + self.assertEqual(qct.depth(), 17) + + def test_invert_and_transpose(self): + """Test that the plugin takes the use_inverted and use_transposed arguments into account.""" + + linear_function = LinearFunction(self.construct_linear_circuit(7)) + + qc = QuantumCircuit(7) + qc.append(linear_function, [0, 1, 2, 3, 4, 5, 6]) + + with self.subTest("default"): + hls_config = HLSConfig(linear_function=[("pmh", {})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 12) + self.assertEqual(qct.depth(), 8) + + with self.subTest("invert"): + hls_config = HLSConfig(linear_function=[("pmh", {"use_inverted": True})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 6) + self.assertEqual(qct.depth(), 6) + + with self.subTest("transpose"): + hls_config = HLSConfig(linear_function=[("pmh", {"use_transposed": True})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 6) + self.assertEqual(qct.depth(), 6) + + with self.subTest("invert_and_transpose"): + hls_config = HLSConfig( + linear_function=[("pmh", {"use_inverted": True, "use_transposed": True})] + ) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 6) + self.assertEqual(qct.depth(), 6) + + +class TestKMSSynthesisLinearFunctionPlugin(QiskitTestCase): + """Tests for the KMSSynthesisLinearFunction plugin for synthesizing linear functions.""" + + @staticmethod + def construct_linear_circuit(num_qubits: int): + """Construct linear circuit.""" + qc = QuantumCircuit(num_qubits) + for i in range(1, num_qubits): + qc.cx(i - 1, i) + return qc + + def test_invert_and_transpose(self): + """Test that the plugin takes the use_inverted and use_transposed arguments into account.""" + + linear_function = LinearFunction(self.construct_linear_circuit(7)) + + qc = QuantumCircuit(7) + qc.append(linear_function, [0, 1, 2, 3, 4, 5, 6]) + + with self.subTest("default"): + hls_config = HLSConfig(linear_function=[("kms", {})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 100) + self.assertEqual(qct.depth(), 34) + + with self.subTest("invert"): + hls_config = HLSConfig(linear_function=[("kms", {"use_inverted": True})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 101) + self.assertEqual(qct.depth(), 35) + + with self.subTest("transpose"): + hls_config = HLSConfig(linear_function=[("kms", {"use_transposed": True})]) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 84) + self.assertEqual(qct.depth(), 31) + + with self.subTest("invert_and_transpose"): + hls_config = HLSConfig( + linear_function=[("kms", {"use_inverted": True, "use_transposed": True})] + ) + qct = HighLevelSynthesis(hls_config=hls_config)(qc) + self.assertEqual(LinearFunction(qct), LinearFunction(qc)) + self.assertEqual(qct.size(), 87) + self.assertEqual(qct.depth(), 32) + + class TestTokenSwapperPermutationPlugin(QiskitTestCase): """Tests for the token swapper plugin for synthesizing permutation gates.""" From b9703488e71b53ed38294af0994105c16c2f9191 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Tue, 23 Apr 2024 07:15:51 +0400 Subject: [PATCH 013/179] Add mapping-like features to data_bin (#12129) * add mapping features to data_bin * Update qiskit/primitives/containers/data_bin.py Co-authored-by: Ian Hincks * add values * change iterable with list * Update qiskit/primitives/containers/data_bin.py Co-authored-by: Ian Hincks * Apply suggestions from code review Co-authored-by: Christopher J. Wood * reno * change return types --------- Co-authored-by: Ian Hincks Co-authored-by: Christopher J. Wood --- qiskit/primitives/containers/data_bin.py | 40 +++++++++++-- .../databin-mapping-45d24d71f9bb4eda.yaml | 24 ++++++++ .../primitives/containers/test_data_bin.py | 59 +++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml diff --git a/qiskit/primitives/containers/data_bin.py b/qiskit/primitives/containers/data_bin.py index 061b80d08bca..50934b6cdfd3 100644 --- a/qiskit/primitives/containers/data_bin.py +++ b/qiskit/primitives/containers/data_bin.py @@ -15,8 +15,9 @@ """ from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from dataclasses import make_dataclass +from typing import Any class DataBinMeta(type): @@ -40,7 +41,15 @@ class DataBin(metaclass=DataBinMeta): :class:`make_dataclass`. """ - _RESTRICTED_NAMES = ("_RESTRICTED_NAMES", "_SHAPE", "_FIELDS", "_FIELD_TYPES") + _RESTRICTED_NAMES = { + "_RESTRICTED_NAMES", + "_SHAPE", + "_FIELDS", + "_FIELD_TYPES", + "keys", + "values", + "items", + } _SHAPE: tuple[int, ...] | None = None _FIELDS: tuple[str, ...] = () """The fields allowed in this data bin.""" @@ -54,10 +63,33 @@ def __repr__(self): vals = (f"{name}={getattr(self, name)}" for name in self._FIELDS if hasattr(self, name)) return f"{type(self)}({', '.join(vals)})" + def __getitem__(self, key: str) -> Any: + if key not in self._FIELDS: + raise KeyError(f"Key ({key}) does not exist in this data bin.") + return getattr(self, key) + + def __contains__(self, key: str) -> bool: + return key in self._FIELDS + + def __iter__(self) -> Iterable[str]: + return iter(self._FIELDS) + + def keys(self) -> Sequence[str]: + """Return a list of field names.""" + return tuple(self._FIELDS) + + def values(self) -> Sequence[Any]: + """Return a list of values.""" + return tuple(getattr(self, key) for key in self._FIELDS) + + def items(self) -> Sequence[tuple[str, Any]]: + """Return a list of field names and values""" + return tuple((key, getattr(self, key)) for key in self._FIELDS) + def make_data_bin( fields: Iterable[tuple[str, type]], shape: tuple[int, ...] | None = None -) -> DataBinMeta: +) -> type[DataBin]: """Return a new subclass of :class:`~DataBin` with the provided fields and shape. .. code-block:: python @@ -74,7 +106,7 @@ def make_data_bin( Returns: A new class. """ - field_names, field_types = zip(*fields) if fields else ([], []) + field_names, field_types = zip(*fields) if fields else ((), ()) for name in field_names: if name in DataBin._RESTRICTED_NAMES: raise ValueError(f"'{name}' is a restricted name for a DataBin.") diff --git a/releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml b/releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml new file mode 100644 index 000000000000..fffdb8562ea9 --- /dev/null +++ b/releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml @@ -0,0 +1,24 @@ +--- +features_primitives: + - | + Added mapping-like features to :class:`~.DataBin`, i.e., + ``__getitem__``, ``__contains__``, ``__iter__``, + :meth:`~.DataBin.keys`, :meth:`~.DataBin.values`, and + :meth:`~.DataBin.items`. + + .. code-block:: python + + from qiskit import QuantumCircuit + from qiskit.primitives import StatevectorSampler + + circuit = QuantumCircuit(1) + circuit.h(0) + circuit.measure_all() + + sampler = StatevectorSampler() + result = sampler.run([circuit]).result() + databin = result[0].data + for creg, arr in databin.items(): + print(creg, arr) + for creg in databin: + print(creg, databin[creg]) diff --git a/test/python/primitives/containers/test_data_bin.py b/test/python/primitives/containers/test_data_bin.py index 59ba1cd71f1d..b750174b7b6f 100644 --- a/test/python/primitives/containers/test_data_bin.py +++ b/test/python/primitives/containers/test_data_bin.py @@ -69,3 +69,62 @@ def test_make_databin_no_fields(self): data_bin_cls = make_data_bin([]) data_bin = data_bin_cls() self.assertEqual(len(data_bin), 0) + + def test_make_databin_mapping(self): + """Test the make_data_bin() function with mapping features.""" + data_bin_cls = make_data_bin([("alpha", int), ("beta", dict)]) + data_bin = data_bin_cls(10, {1: 2}) + self.assertEqual(len(data_bin), 2) + + with self.subTest("iterator"): + iterator = iter(data_bin) + key = next(iterator) + self.assertEqual(key, "alpha") + key = next(iterator) + self.assertEqual(key, "beta") + with self.assertRaises(StopIteration): + _ = next(iterator) + + with self.subTest("keys"): + lst = data_bin.keys() + key = lst[0] + self.assertEqual(key, "alpha") + key = lst[1] + self.assertEqual(key, "beta") + + with self.subTest("values"): + lst = data_bin.values() + val = lst[0] + self.assertIsInstance(val, int) + self.assertEqual(val, 10) + val = lst[1] + self.assertIsInstance(val, dict) + self.assertEqual(val, {1: 2}) + + with self.subTest("items"): + lst = data_bin.items() + key, val = lst[0] + self.assertEqual(key, "alpha") + self.assertIsInstance(val, int) + self.assertEqual(val, 10) + key, val = lst[1] + self.assertEqual(key, "beta") + self.assertIsInstance(val, dict) + self.assertEqual(val, {1: 2}) + + with self.subTest("contains"): + self.assertIn("alpha", data_bin) + self.assertIn("beta", data_bin) + self.assertNotIn("gamma", data_bin) + + with self.subTest("getitem"): + val = data_bin["alpha"] + self.assertIsInstance(val, int) + self.assertEqual(val, 10) + val = data_bin["beta"] + self.assertIsInstance(val, dict) + self.assertEqual(val, {1: 2}) + + with self.subTest("error"): + with self.assertRaises(KeyError): + _ = data_bin["gamma"] From 40ac2744c1a791a25d4f6a0356b150ecc42edb70 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 23 Apr 2024 00:06:11 -0400 Subject: [PATCH 014/179] Increase heuristic effort for optimization level 2 (#12149) * Increase heuristic effort for optimization level 2 This commit tweaks the heuristic effort in optimization level 2 to be more of a middle ground between level 1 and 3; with a better balance between output quality and runtime. This places it to be a better default for a pass manager we use if one isn't specified. The tradeoff here is that the vf2layout and vf2postlayout search space is reduced to be the same as level 1. There are diminishing margins of return on the vf2 layout search especially for cases when there are a large number of qubit permutations for the mapping found. Then the number of sabre trials is brought up to the same level as optimization level 3. As this can have a significant impact on output and the extra runtime cost is minimal. The larger change is that the optimization passes from level 3. This ends up mainly being 2q peephole optimization. With the performance improvements from #12010 and #11946 and all the follow-on PRs this is now fast enough to rely on in optimization level 2. * Add test workaround from level 3 to level 2 too * Expand vf2 call limit on VF2Layout For the initial VF2Layout call this commit expands the vf2 call limit back to the previous level instead of reducing it to the same as level 1. The idea behind making this change is that spending up to 10s to find a perfect layout is a worthwhile tradeoff as that will greatly improve the result from execution. But scoring multiple layouts to find the lowest error rate subgraph has a diminishing margin of return in most cases as there typically aren't thousands of unique subgraphs and often when we hit the scoring limit it's just permuting the qubits inside a subgraph which doesn't provide the most value. For VF2PostLayout the lower call limits from level 1 is still used. This is because both the search for isomorphic subgraphs is typically much shorter with the vf2++ node ordering heuristic so we don't need to spend as much time looking for alternative subgraphs. * Move 2q peephole outside of optimization loop in O2 Due to potential instability in the 2q peephole optimization we run we were using the `MinimumPoint` pass to provide backtracking when we reach a local minimum. However, this pass adds a significant amount of overhead because it deep copies the circuit at every iteration of the optimization loop that improves the output quality. This commit tweaks the O2 pass manager construction to only run 2q peephole once, and then updates the optimization loop to be what the previous O2 optimization loop was. --- .../preset_passmanagers/builtin_plugins.py | 44 +++++++++++++------ .../transpiler/preset_passmanagers/common.py | 7 +-- .../optimization-level2-2c8c1488173aed31.yaml | 10 +++++ test/python/compiler/test_transpiler.py | 5 ++- 4 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 7360825b9747..fc01f6efaced 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -87,7 +87,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.unitary_synthesis_plugin_config, pass_manager_config.hls_config, ) - elif optimization_level in {1, 2}: + elif optimization_level == 1: init = PassManager() if ( pass_manager_config.initial_layout @@ -123,10 +123,8 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ] ) ) - if optimization_level == 2: - init.append(CommutativeCancellation()) - elif optimization_level == 3: + elif optimization_level in {2, 3}: init = common.generate_unroll_3q( pass_manager_config.target, pass_manager_config.basis_gates, @@ -543,16 +541,13 @@ def _opt_control(property_set): ] ), ] + elif optimization_level == 2: - # Steps for optimization level 2 _opt = [ Optimize1qGatesDecomposition( basis=pass_manager_config.basis_gates, target=pass_manager_config.target ), - CommutativeCancellation( - basis_gates=pass_manager_config.basis_gates, - target=pass_manager_config.target, - ), + CommutativeCancellation(target=pass_manager_config.target), ] elif optimization_level == 3: # Steps for optimization level 3 @@ -598,6 +593,27 @@ def _unroll_condition(property_set): if optimization_level == 3: optimization.append(_minimum_point_check) + elif optimization_level == 2: + optimization.append( + [ + Collect2qBlocks(), + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), + ] + ) + optimization.append(_depth_check + _size_check) else: optimization.append(_depth_check + _size_check) opt_loop = ( @@ -749,7 +765,7 @@ def _swap_mapped(property_set): call_limit=int(5e6), # Set call limit to ~10s with rustworkx 0.10.2 properties=pass_manager_config.backend_properties, target=pass_manager_config.target, - max_trials=25000, # Limits layout scoring to < 10s on ~400 qubit devices + max_trials=2500, # Limits layout scoring to < 600ms on ~400 qubit devices ) layout.append( ConditionalController(choose_layout_0, condition=_choose_layout_condition) @@ -758,8 +774,8 @@ def _swap_mapped(property_set): coupling_map, max_iterations=2, seed=pass_manager_config.seed_transpiler, - swap_trials=10, - layout_trials=10, + swap_trials=20, + layout_trials=20, skip_routing=pass_manager_config.routing_method is not None and pass_manager_config.routing_method != "sabre", ) @@ -911,8 +927,8 @@ def _swap_mapped(property_set): coupling_map, max_iterations=2, seed=pass_manager_config.seed_transpiler, - swap_trials=10, - layout_trials=10, + swap_trials=20, + layout_trials=20, skip_routing=pass_manager_config.routing_method is not None and pass_manager_config.routing_method != "sabre", ) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index dcd9f27c2bc0..1b77e7dbd269 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -627,16 +627,11 @@ def get_vf2_limits( """ limits = VF2Limits(None, None) if layout_method is None and initial_layout is None: - if optimization_level == 1: + if optimization_level in {1, 2}: limits = VF2Limits( int(5e4), # Set call limit to ~100ms with rustworkx 0.10.2 2500, # Limits layout scoring to < 600ms on ~400 qubit devices ) - elif optimization_level == 2: - limits = VF2Limits( - int(5e6), # Set call limit to ~10 sec with rustworkx 0.10.2 - 25000, # Limits layout scoring to < 6 sec on ~400 qubit devices - ) elif optimization_level == 3: limits = VF2Limits( int(3e7), # Set call limit to ~60 sec with rustworkx 0.10.2 diff --git a/releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml b/releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml new file mode 100644 index 000000000000..20c9b1ed9cbb --- /dev/null +++ b/releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml @@ -0,0 +1,10 @@ +--- +upgrade_transpiler: + - | + The preset :class:`.StagedPassManager` returned for optimization level 2 by + :func:`.generate_preset_pass_manager` and :func:`.level_2_pass_manager` have + been reworked to provide a better balance between runtime and optimization. + This means the output circuits will change compared to earlier releases. If + you need an exact pass manager from level 2 in earlier releases you can + either build it manually or use it from an earlier release and save the + circuits with :mod:`~qiskit.qpy` to load with a newer release. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index d4e19da541a0..943de7b932e7 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1831,11 +1831,12 @@ def test_synthesis_translation_method_with_gates_outside_basis(self, optimizatio optimization_level=optimization_level, seed_transpiler=42, ) - if optimization_level != 3: + if optimization_level not in {2, 3}: self.assertTrue(Operator(qc).equiv(res)) self.assertNotIn("swap", res.count_ops()) else: - # Optimization level 3 eliminates the pointless swap + # Optimization level 2 and 3 eliminates the swap by permuting the + # qubits self.assertEqual(res, QuantumCircuit(2)) @data(0, 1, 2, 3) From 291fa09b5e608ef5768246229c3a0a4d365bae7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:24:20 +0200 Subject: [PATCH 015/179] Remove already "removed" `FakeBackendV2` (#11997) * Remove FakeBackendV2 * Fix lint --- .../providers/fake_provider/fake_backend.py | 411 +----------------- 1 file changed, 3 insertions(+), 408 deletions(-) diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py index 449ab20ae4dd..d84aba46371d 100644 --- a/qiskit/providers/fake_provider/fake_backend.py +++ b/qiskit/providers/fake_provider/fake_backend.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2023. +# (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 @@ -17,28 +17,14 @@ """ import warnings -import collections -import json -import os -import re - -from typing import List, Iterable from qiskit import circuit -from qiskit.providers.models import BackendProperties, BackendConfiguration, PulseDefaults -from qiskit.providers import BackendV2, BackendV1 +from qiskit.providers.models import BackendProperties +from qiskit.providers import BackendV1 from qiskit import pulse from qiskit.exceptions import QiskitError from qiskit.utils import optionals as _optionals from qiskit.providers import basic_provider -from qiskit.transpiler import Target -from qiskit.providers.backend_compat import convert_to_target - -from .utils.json_decoder import ( - decode_backend_configuration, - decode_backend_properties, - decode_pulse_defaults, -) class _Credentials: @@ -50,397 +36,6 @@ def __init__(self, token="123456", url="https://"): self.project = "project" -class FakeBackendV2(BackendV2): - """A fake backend class for testing and noisy simulation using real backend - snapshots. - - The class inherits :class:`~qiskit.providers.BackendV2` class. This version - differs from earlier :class:`~qiskit.providers.fake_provider.FakeBackend` (V1) class in a - few aspects. Firstly, configuration attribute no longer exsists. Instead, - attributes exposing equivalent required immutable properties of the backend - device are added. For example ``fake_backend.configuration().n_qubits`` is - accessible from ``fake_backend.num_qubits`` now. Secondly, this version - removes extra abstractions :class:`~qiskit.providers.fake_provider.FakeQasmBackend` and - :class:`~qiskit.providers.fake_provider.FakePulseBackend` that were present in V1. - """ - - # directory and file names for real backend snapshots. - dirname = None - conf_filename = None - props_filename = None - defs_filename = None - backend_name = None - - def __init__(self): - """FakeBackendV2 initializer.""" - self._conf_dict = self._get_conf_dict_from_json() - self._props_dict = None - self._defs_dict = None - super().__init__( - provider=None, - name=self._conf_dict.get("backend_name"), - description=self._conf_dict.get("description"), - online_date=self._conf_dict.get("online_date"), - backend_version=self._conf_dict.get("backend_version"), - ) - self._target = None - self.sim = None - - if "channels" in self._conf_dict: - self._parse_channels(self._conf_dict["channels"]) - - def _parse_channels(self, channels): - type_map = { - "acquire": pulse.AcquireChannel, - "drive": pulse.DriveChannel, - "measure": pulse.MeasureChannel, - "control": pulse.ControlChannel, - } - identifier_pattern = re.compile(r"\D+(?P\d+)") - - channels_map = { - "acquire": collections.defaultdict(list), - "drive": collections.defaultdict(list), - "measure": collections.defaultdict(list), - "control": collections.defaultdict(list), - } - for identifier, spec in channels.items(): - channel_type = spec["type"] - out = re.match(identifier_pattern, identifier) - if out is None: - # Identifier is not a valid channel name format - continue - channel_index = int(out.groupdict()["index"]) - qubit_index = tuple(spec["operates"]["qubits"]) - chan_obj = type_map[channel_type](channel_index) - channels_map[channel_type][qubit_index].append(chan_obj) - setattr(self, "channels_map", channels_map) - - def _setup_sim(self): - if _optionals.HAS_AER: - from qiskit_aer import AerSimulator - - self.sim = AerSimulator() - if self.target and self._props_dict: - noise_model = self._get_noise_model_from_backend_v2() - self.sim.set_options(noise_model=noise_model) - # Update fake backend default too to avoid overwriting - # it when run() is called - self.set_options(noise_model=noise_model) - - else: - self.sim = basic_provider.BasicSimulator() - - def _get_conf_dict_from_json(self): - if not self.conf_filename: - return None - conf_dict = self._load_json(self.conf_filename) - decode_backend_configuration(conf_dict) - conf_dict["backend_name"] = self.backend_name - return conf_dict - - def _set_props_dict_from_json(self): - if self.props_filename: - props_dict = self._load_json(self.props_filename) - decode_backend_properties(props_dict) - self._props_dict = props_dict - - def _set_defs_dict_from_json(self): - if self.defs_filename: - defs_dict = self._load_json(self.defs_filename) - decode_pulse_defaults(defs_dict) - self._defs_dict = defs_dict - - def _load_json(self, filename: str) -> dict: - with open(os.path.join(self.dirname, filename)) as f_json: - the_json = json.load(f_json) - return the_json - - @property - def target(self) -> Target: - """A :class:`qiskit.transpiler.Target` object for the backend. - - :rtype: Target - """ - if self._target is None: - self._get_conf_dict_from_json() - if self._props_dict is None: - self._set_props_dict_from_json() - if self._defs_dict is None: - self._set_defs_dict_from_json() - conf = BackendConfiguration.from_dict(self._conf_dict) - props = None - if self._props_dict is not None: - props = BackendProperties.from_dict(self._props_dict) - defaults = None - if self._defs_dict is not None: - defaults = PulseDefaults.from_dict(self._defs_dict) - - self._target = convert_to_target( - configuration=conf, properties=props, defaults=defaults - ) - return self._target - - @property - def max_circuits(self): - return None - - @classmethod - def _default_options(cls): - """Return the default options - - This method will return a :class:`qiskit.providers.Options` - subclass object that will be used for the default options. These - should be the default parameters to use for the options of the - backend. - - Returns: - qiskit.providers.Options: A options object with - default values set - """ - if _optionals.HAS_AER: - from qiskit_aer import AerSimulator - - return AerSimulator._default_options() - else: - return basic_provider.BasicSimulator._default_options() - - @property - def dtm(self) -> float: - """Return the system time resolution of output signals - - Returns: - The output signal timestep in seconds. - """ - dtm = self._conf_dict.get("dtm") - if dtm is not None: - # converting `dtm` in nanoseconds in configuration file to seconds - return dtm * 1e-9 - else: - return None - - @property - def meas_map(self) -> List[List[int]]: - """Return the grouping of measurements which are multiplexed - This is required to be implemented if the backend supports Pulse - scheduling. - - Returns: - The grouping of measurements which are multiplexed - """ - return self._conf_dict.get("meas_map") - - def drive_channel(self, qubit: int): - """Return the drive channel for the given qubit. - - This is required to be implemented if the backend supports Pulse - scheduling. - - Returns: - DriveChannel: The Qubit drive channel - """ - drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) - qubits = (qubit,) - if qubits in drive_channels_map: - return drive_channels_map[qubits][0] - return None - - def measure_channel(self, qubit: int): - """Return the measure stimulus channel for the given qubit. - - This is required to be implemented if the backend supports Pulse - scheduling. - - Returns: - MeasureChannel: The Qubit measurement stimulus line - """ - measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) - qubits = (qubit,) - if qubits in measure_channels_map: - return measure_channels_map[qubits][0] - return None - - def acquire_channel(self, qubit: int): - """Return the acquisition channel for the given qubit. - - This is required to be implemented if the backend supports Pulse - scheduling. - - Returns: - AcquireChannel: The Qubit measurement acquisition line. - """ - acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) - qubits = (qubit,) - if qubits in acquire_channels_map: - return acquire_channels_map[qubits][0] - return None - - def control_channel(self, qubits: Iterable[int]): - """Return the secondary drive channel for the given qubit - - This is typically utilized for controlling multiqubit interactions. - This channel is derived from other channels. - - This is required to be implemented if the backend supports Pulse - scheduling. - - Args: - qubits: Tuple or list of qubits of the form - ``(control_qubit, target_qubit)``. - - Returns: - List[ControlChannel]: The multi qubit control line. - """ - control_channels_map = getattr(self, "channels_map", {}).get("control", {}) - qubits = tuple(qubits) - if qubits in control_channels_map: - return control_channels_map[qubits] - return [] - - def run(self, run_input, **options): - """Run on the fake backend using a simulator. - - This method runs circuit jobs (an individual or a list of QuantumCircuit - ) and pulse jobs (an individual or a list of Schedule or ScheduleBlock) - using a :class:`.BasicSimulator` or Aer simulator and returns a - :class:`~qiskit.providers.Job` object. - - If qiskit-aer is installed, jobs will be run using AerSimulator with - noise model of the fake backend. Otherwise, jobs will be run using - :class:`.BasicSimulator` without noise. - - Currently noisy simulation of a pulse job is not supported yet in - FakeBackendV2. - - Args: - run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An - individual or a list of - :class:`~qiskit.circuit.QuantumCircuit`, - :class:`~qiskit.pulse.ScheduleBlock`, or - :class:`~qiskit.pulse.Schedule` objects to run on the backend. - options: Any kwarg options to pass to the backend for running the - config. If a key is also present in the options - attribute/object then the expectation is that the value - specified will be used instead of what's set in the options - object. - - Returns: - Job: The job object for the run - - Raises: - QiskitError: If a pulse job is supplied and qiskit-aer is not installed. - """ - circuits = run_input - pulse_job = None - if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): - pulse_job = True - elif isinstance(circuits, circuit.QuantumCircuit): - pulse_job = False - elif isinstance(circuits, list): - if circuits: - if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits): - pulse_job = True - elif all(isinstance(x, circuit.QuantumCircuit) for x in circuits): - pulse_job = False - if pulse_job is None: # submitted job is invalid - raise QiskitError( - "Invalid input object %s, must be either a " - "QuantumCircuit, Schedule, or a list of either" % circuits - ) - if pulse_job: # pulse job - raise QiskitError("Pulse simulation is currently not supported for fake backends.") - # circuit job - if not _optionals.HAS_AER: - warnings.warn("Aer not found using BasicProvider and no noise", RuntimeWarning) - if self.sim is None: - self._setup_sim() - self.sim._options = self._options - job = self.sim.run(circuits, **options) - return job - - def _get_noise_model_from_backend_v2( - self, - gate_error=True, - readout_error=True, - thermal_relaxation=True, - temperature=0, - gate_lengths=None, - gate_length_units="ns", - ): - """Build noise model from BackendV2. - - This is a temporary fix until qiskit-aer supports building noise model - from a BackendV2 object. - """ - - from qiskit.circuit import Delay - from qiskit.providers.exceptions import BackendPropertyError - from qiskit_aer.noise import NoiseModel - from qiskit_aer.noise.device.models import ( - _excited_population, - basic_device_gate_errors, - basic_device_readout_errors, - ) - from qiskit_aer.noise.passes import RelaxationNoisePass - - if self._props_dict is None: - self._set_props_dict_from_json() - - properties = BackendProperties.from_dict(self._props_dict) - basis_gates = self.operation_names - num_qubits = self.num_qubits - dt = self.dt - - noise_model = NoiseModel(basis_gates=basis_gates) - - # Add single-qubit readout errors - if readout_error: - for qubits, error in basic_device_readout_errors(properties): - noise_model.add_readout_error(error, qubits) - - # Add gate errors - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - module="qiskit_aer.noise.device.models", - ) - gate_errors = basic_device_gate_errors( - properties, - gate_error=gate_error, - thermal_relaxation=thermal_relaxation, - gate_lengths=gate_lengths, - gate_length_units=gate_length_units, - temperature=temperature, - ) - for name, qubits, error in gate_errors: - noise_model.add_quantum_error(error, name, qubits) - - if thermal_relaxation: - # Add delay errors via RelaxationNiose pass - try: - excited_state_populations = [ - _excited_population(freq=properties.frequency(q), temperature=temperature) - for q in range(num_qubits) - ] - except BackendPropertyError: - excited_state_populations = None - try: - delay_pass = RelaxationNoisePass( - t1s=[properties.t1(q) for q in range(num_qubits)], - t2s=[properties.t2(q) for q in range(num_qubits)], - dt=dt, - op_types=Delay, - excited_state_populations=excited_state_populations, - ) - noise_model._custom_noise_passes.append(delay_pass) - except BackendPropertyError: - # Device does not have the required T1 or T2 information - # in its properties - pass - - return noise_model - - class FakeBackend(BackendV1): """This is a dummy backend just for testing purposes.""" From 4d95d9419b4e3fa4e0d83e0522324c08fb83ae10 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 23 Apr 2024 17:39:21 +0200 Subject: [PATCH 016/179] Deprecating Provider ABC, as abstraction is not very useful (#12145) * Provider abstraction is not very useful * udpate docs * ignore deprecations * not triggered on CI * deprecation warnings in visual tests * set up * set up without warning? * setUpClass * more test adjust * raise at setUpClass * warms * test_circuit_matplotlib_drawer.py * skip Aer warning * Apply suggestions from code review Co-authored-by: Matthew Treinish * reno * Run black * Update release note * linter --------- Co-authored-by: Matthew Treinish --- qiskit/providers/__init__.py | 35 +++++++++---------- qiskit/providers/backend.py | 4 +-- .../fake_provider/generic_backend_v2.py | 16 ++++++--- qiskit/providers/provider.py | 16 +++++++++ ...deprecate_providerV1-ba17d7b4639d1cc5.yaml | 18 ++++++++++ .../python/primitives/test_backend_sampler.py | 3 +- .../test_basic_provider_backends.py | 9 +++-- .../test_multi_registers_convention.py | 3 +- test/python/transpiler/test_sabre_swap.py | 3 +- test/utils/base.py | 3 +- 10 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index 029d335695d5..19d300c9bbf6 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -17,13 +17,13 @@ .. currentmodule:: qiskit.providers -This module contains the classes used to build external providers for Terra. A -provider is anything that provides an external service to Terra. The typical +This module contains the classes used to build external providers for Qiskit. A +provider is anything that provides an external service to Qiskit. The typical example of this is a Backend provider which provides :class:`~qiskit.providers.Backend` objects which can be used for executing :class:`~qiskit.circuit.QuantumCircuit` and/or :class:`~qiskit.pulse.Schedule` objects. This module contains the abstract classes which are used to define the -interface between a provider and terra. +interface between a provider and Qiskit. Version Support =============== @@ -36,17 +36,17 @@ Version Changes ---------------- -Each minor version release of qiskit-terra **may** increment the version of any -providers interface a single version number. It will be an aggregate of all +Each minor version release of ``qiskit`` **may** increment the version of any +backend interface a single version number. It will be an aggregate of all the interface changes for that release on that interface. Version Support Policy ---------------------- To enable providers to have time to adjust to changes in this interface -Terra will support multiple versions of each class at once. Given the +Qiskit will support multiple versions of each class at once. Given the nature of one version per release the version deprecation policy is a bit -more conservative than the standard deprecation policy. Terra will support a +more conservative than the standard deprecation policy. Qiskit will support a provider interface version for a minimum of 3 minor releases or the first release after 6 months from the release that introduced a version, whichever is longer, prior to a potential deprecation. After that the standard deprecation @@ -57,17 +57,17 @@ 0.19.0 we release 0.23.0. In 0.23.0 we can deprecate BackendV2, and it needs to still be supported and can't be removed until the deprecation policy completes. -It's worth pointing out that Terra's version support policy doesn't mean +It's worth pointing out that Qiskit's version support policy doesn't mean providers themselves will have the same support story, they can (and arguably should) update to newer versions as soon as they can, the support window is -just for Terra's supported versions. Part of this lengthy window prior to +just for Qiskit's supported versions. Part of this lengthy window prior to deprecation is to give providers enough time to do their own deprecation of a potential end user impacting change in a user facing part of the interface prior to bumping their version. For example, let's say we changed the signature to ``Backend.run()`` in ``BackendV34`` in a backwards incompatible way. Before Aer could update its :class:`~qiskit_aer.AerSimulator` class to be based on version 34 they'd need to deprecate the old signature prior to switching -over. The changeover for Aer is not guaranteed to be lockstep with Terra so we +over. The changeover for Aer is not guaranteed to be lockstep with Qiskit, so we need to ensure there is a sufficient amount of time for Aer to complete its deprecation cycle prior to removing version 33 (ie making version 34 mandatory/the minimum version). @@ -131,12 +131,13 @@ .. autoexception:: JobTimeoutError .. autoexception:: BackendConfigurationError -====================== -Writing a New Provider -====================== +===================== +Writing a New Backend +===================== If you have a quantum device or simulator that you would like to integrate with -Qiskit you will need to write a provider. A provider will provide Terra with a +Qiskit you will need to write a backend. A provider is a collection of backends +and will provide Qiskit with a method to get available :class:`~qiskit.providers.BackendV2` objects. The :class:`~qiskit.providers.BackendV2` object provides both information describing a backend and its operation for the :mod:`~qiskit.transpiler` so that circuits @@ -149,8 +150,7 @@ fashion regardless of how the backend is implemented. At a high level the basic steps for writing a provider are: - * Implement a :class:`~qiskit.providers.ProviderV1` subclass that handles - access to the backend(s). + * Implement a ``Provider`` class that handles access to the backend(s). * Implement a :class:`~qiskit.providers.BackendV2` subclass and its :meth:`~qiskit.providers.BackendV2.run` method. @@ -173,12 +173,11 @@ and methods to filter and acquire backends (using the provided credentials if required). An example provider class looks like:: - from qiskit.providers import ProviderV1 as Provider from qiskit.providers.providerutils import filter_backends from .backend import MyBackend - class MyProvider(Provider): + class MyProvider: def __init__(self, token=None): super().__init__() diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 5ff10b5f7f74..8ffc7765109c 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -40,8 +40,8 @@ class Backend: class BackendV1(Backend, ABC): """Abstract class for Backends - This abstract class is to be used for all Backend objects created by a - provider. There are several classes of information contained in a Backend. + This abstract class is to be used for Backend objects. + There are several classes of information contained in a Backend. The first are the attributes of the class itself. These should be used to defined the immutable characteristics of the backend. The ``options`` attribute of the backend is used to contain the dynamic user configurable diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index b8f40ff1a275..e806c75ea3a5 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -526,12 +526,18 @@ def _setup_sim(self) -> None: @classmethod def _default_options(cls) -> Options: - if _optionals.HAS_AER: - from qiskit_aer import AerSimulator + with warnings.catch_warnings(): # TODO remove catch once aer release without Provider ABC + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=".+abstract Provider and ProviderV1.+", + ) + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator - return AerSimulator._default_options() - else: - return BasicSimulator._default_options() + return AerSimulator._default_options() + else: + return BasicSimulator._default_options() def drive_channel(self, qubit: int): drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) diff --git a/qiskit/providers/provider.py b/qiskit/providers/provider.py index 9bea69c1ea85..dc836e7f25ee 100644 --- a/qiskit/providers/provider.py +++ b/qiskit/providers/provider.py @@ -15,6 +15,7 @@ from abc import ABC, abstractmethod from qiskit.providers.exceptions import QiskitBackendNotFoundError +from qiskit.utils import deprecate_func class Provider: @@ -28,12 +29,27 @@ class Provider: version = 0 + @deprecate_func( + since=1.1, + additional_msg="The abstract Provider and ProviderV1 classes are deprecated and will be " + "removed in 2.0. You can just remove it as the parent class and a `get_backend` " + "method that returns the backends from `self.backend`.", + ) + def __init__(self): + pass + class ProviderV1(Provider, ABC): """Base class for a Backend Provider.""" version = 1 + @deprecate_func( + since=1.1, + additional_msg="The abstract Provider and ProviderV1 classes are deprecated and will be " + "removed in 2.0. You can just remove it as the parent class and a `get_backend` " + "method that returns the backends from `self.backend`.", + ) def get_backend(self, name=None, **kwargs): """Return a single backend matching the specified filtering. diff --git a/releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml b/releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml new file mode 100644 index 000000000000..bfec8ef89041 --- /dev/null +++ b/releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml @@ -0,0 +1,18 @@ +--- +deprecations_providers: + - | + The abstract base classes ``Provider`` and ``ProviderV1`` are now deprecated and will be removed in Qiskit 2.0.0. + The abstraction provided by these interface definitions were not providing a huge value. solely just the attributes + ``name``, ``backends``, and a ``get_backend()``. A _provider_, as a concept, will continue existing as a collection + of backends. If you're implementing a provider currently you can adjust your + code by simply removing ``ProviderV1`` as the parent class of your + implementation. As part of this you probably would want to add an + implementation of ``get_backend`` for backwards compatibility. For example:: + + def get_backend(self, name=None, **kwargs): + backend = self.backends(name, **kwargs) + if len(backends) > 1: + raise QiskitBackendNotFoundError("More than one backend matches the criteria") + if not backends: + raise QiskitBackendNotFoundError("No backend matches the criteria") + return backends[0] diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index b455e6b5dc17..8d1a3e2aab2b 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -352,7 +352,8 @@ def test_circuit_with_dynamic_circuit(self): qc.measure(0, 0) qc.break_loop().c_if(0, True) - backend = Aer.get_backend("aer_simulator") + with self.assertWarns(DeprecationWarning): + backend = Aer.get_backend("aer_simulator") sampler = BackendSampler(backend, skip_transpilation=True) sampler.set_options(seed_simulator=15) sampler.set_transpile_options(seed_transpiler=15) diff --git a/test/python/providers/basic_provider/test_basic_provider_backends.py b/test/python/providers/basic_provider/test_basic_provider_backends.py index d761015d04e1..5c00ec87671a 100644 --- a/test/python/providers/basic_provider/test_basic_provider_backends.py +++ b/test/python/providers/basic_provider/test_basic_provider_backends.py @@ -22,7 +22,8 @@ class TestBasicProviderBackends(QiskitTestCase): def setUp(self): super().setUp() - self.provider = BasicProvider() + with self.assertWarns(DeprecationWarning): + self.provider = BasicProvider() self.backend_name = "basic_simulator" def test_backends(self): @@ -32,9 +33,11 @@ def test_backends(self): def test_get_backend(self): """Test getting a backend from the provider.""" - backend = self.provider.get_backend(name=self.backend_name) + with self.assertWarns(DeprecationWarning): + backend = self.provider.get_backend(name=self.backend_name) self.assertEqual(backend.name, self.backend_name) def test_aliases_fail(self): """Test a failing backend lookup.""" - self.assertRaises(QiskitBackendNotFoundError, BasicProvider().get_backend, "bad_name") + with self.assertWarns(DeprecationWarning): + self.assertRaises(QiskitBackendNotFoundError, BasicProvider().get_backend, "bad_name") diff --git a/test/python/providers/basic_provider/test_multi_registers_convention.py b/test/python/providers/basic_provider/test_multi_registers_convention.py index a6d114f6b5b7..8ebf559f938c 100644 --- a/test/python/providers/basic_provider/test_multi_registers_convention.py +++ b/test/python/providers/basic_provider/test_multi_registers_convention.py @@ -36,7 +36,8 @@ def test_circuit_multi(self): qc = circ.compose(meas) - backend_sim = BasicProvider().get_backend("basic_simulator") + with self.assertWarns(DeprecationWarning): + backend_sim = BasicProvider().get_backend("basic_simulator") result = backend_sim.run(qc).result() counts = result.get_counts(qc) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index e63565c14b05..a9fec85be665 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -278,7 +278,8 @@ def test_no_infinite_loop(self, method): from qiskit_aer import Aer - sim = Aer.get_backend("aer_simulator") + with self.assertWarns(DeprecationWarning): + sim = Aer.get_backend("aer_simulator") in_results = sim.run(qc, shots=4096).result().get_counts() out_results = sim.run(routed, shots=4096).result().get_counts() self.assertEqual(set(in_results), set(out_results)) diff --git a/test/utils/base.py b/test/utils/base.py index 1b8be97641a8..747be7f66b51 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -174,7 +174,8 @@ def tearDown(self): # due to importing the instances from the top-level qiskit namespace. from qiskit.providers.basic_provider import BasicProvider - BasicProvider()._backends = BasicProvider()._verify_backends() + with self.assertWarns(DeprecationWarning): + BasicProvider()._backends = BasicProvider()._verify_backends() @classmethod def setUpClass(cls): From cf57a98740d061f50e68492a481d165fad55df0d Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Tue, 23 Apr 2024 21:56:37 +0300 Subject: [PATCH 017/179] Adding annotated argument to power methods (#12101) * adding annotated argument to power methods * release notes * Apply suggestions from code review --------- Co-authored-by: Matthew Treinish --- qiskit/circuit/annotated_operation.py | 18 ++++++++++ qiskit/circuit/gate.py | 27 ++++++++++---- qiskit/circuit/library/standard_gates/i.py | 3 +- .../circuit/library/standard_gates/iswap.py | 3 +- qiskit/circuit/library/standard_gates/p.py | 6 ++-- qiskit/circuit/library/standard_gates/r.py | 3 +- qiskit/circuit/library/standard_gates/rx.py | 3 +- qiskit/circuit/library/standard_gates/rxx.py | 3 +- qiskit/circuit/library/standard_gates/ry.py | 3 +- qiskit/circuit/library/standard_gates/ryy.py | 3 +- qiskit/circuit/library/standard_gates/rz.py | 3 +- qiskit/circuit/library/standard_gates/rzx.py | 3 +- qiskit/circuit/library/standard_gates/rzz.py | 3 +- qiskit/circuit/library/standard_gates/s.py | 12 +++---- qiskit/circuit/library/standard_gates/t.py | 6 ++-- .../library/standard_gates/xx_minus_yy.py | 3 +- .../library/standard_gates/xx_plus_yy.py | 3 +- qiskit/circuit/library/standard_gates/z.py | 3 +- qiskit/circuit/quantumcircuit.py | 36 ++++++++++++------- ...notated-arg-to-power-4afe90e89fa50f5a.yaml | 18 ++++++++++ .../circuit/test_annotated_operation.py | 2 +- .../python/circuit/test_circuit_operations.py | 17 ++++++++- test/python/circuit/test_gate_power.py | 26 +++++++++++++- 23 files changed, 143 insertions(+), 64 deletions(-) create mode 100644 releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml diff --git a/qiskit/circuit/annotated_operation.py b/qiskit/circuit/annotated_operation.py index 1481b24dc0e1..6780cc2e330f 100644 --- a/qiskit/circuit/annotated_operation.py +++ b/qiskit/circuit/annotated_operation.py @@ -201,6 +201,24 @@ def inverse(self, annotated: bool = True): extended_modifiers.append(InverseModifier()) return AnnotatedOperation(self.base_op, extended_modifiers) + def power(self, exponent: float, annotated: bool = False): + """ + Raise this gate to the power of ``exponent``. + + Implemented as an annotated operation, see :class:`.AnnotatedOperation`. + + Args: + exponent: the power to raise the gate to + annotated: ignored (used for consistency with other power methods) + + Returns: + An operation implementing ``gate^exponent`` + """ + # pylint: disable=unused-argument + extended_modifiers = self.modifiers.copy() + extended_modifiers.append(PowerModifier(exponent)) + return AnnotatedOperation(self.base_op, extended_modifiers) + def _canonicalize_modifiers(modifiers): """ diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index af29f8033b44..132526775860 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -18,7 +18,7 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.exceptions import CircuitError -from .annotated_operation import AnnotatedOperation, ControlModifier +from .annotated_operation import AnnotatedOperation, ControlModifier, PowerModifier from .instruction import Instruction @@ -62,23 +62,36 @@ def to_matrix(self) -> np.ndarray: return self.__array__(dtype=complex) raise CircuitError(f"to_matrix not defined for this {type(self)}") - def power(self, exponent: float): - """Creates a unitary gate as `gate^exponent`. + def power(self, exponent: float, annotated: bool = False): + """Raise this gate to the power of ``exponent``. + + Implemented either as a unitary gate (ref. :class:`~.library.UnitaryGate`) + or as an annotated operation (ref. :class:`.AnnotatedOperation`). In the case of several standard + gates, such as :class:`.RXGate`, when the power of a gate can be expressed in terms of another + standard gate that is returned directly. Args: - exponent (float): Gate^exponent + exponent (float): the power to raise the gate to + annotated (bool): indicates whether the power gate can be implemented + as an annotated operation. In the case of several standard + gates, such as :class:`.RXGate`, this argument is ignored when + the power of a gate can be expressed in terms of another + standard gate. Returns: - .library.UnitaryGate: To which `to_matrix` is self.to_matrix^exponent. + An operation implementing ``gate^exponent`` Raises: - CircuitError: If Gate is not unitary + CircuitError: If gate is not unitary """ # pylint: disable=cyclic-import from qiskit.quantum_info.operators import Operator from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate - return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}") + if not annotated: + return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}") + else: + return AnnotatedOperation(self, PowerModifier(exponent)) def __pow__(self, exponent: float) -> "Gate": return self.power(exponent) diff --git a/qiskit/circuit/library/standard_gates/i.py b/qiskit/circuit/library/standard_gates/i.py index 2468d9dcedf5..93523215d6f0 100644 --- a/qiskit/circuit/library/standard_gates/i.py +++ b/qiskit/circuit/library/standard_gates/i.py @@ -65,8 +65,7 @@ def inverse(self, annotated: bool = False): .""" return IGate() # self-inverse - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): return IGate() def __eq__(self, other): diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py index d871f6b8c3de..50d3a6bb3473 100644 --- a/qiskit/circuit/library/standard_gates/iswap.py +++ b/qiskit/circuit/library/standard_gates/iswap.py @@ -124,8 +124,7 @@ def _define(self): self.definition = qc - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): return XXPlusYYGate(-np.pi * exponent) def __eq__(self, other): diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 1db0e7023f13..179be025bcd0 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -145,8 +145,7 @@ def __array__(self, dtype=None): lam = float(self.params[0]) return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return PhaseGate(exponent * theta) @@ -289,8 +288,7 @@ def __array__(self, dtype=None): ) return numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, eith, 0], [0, 0, 0, 1]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return CPhaseGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index a9b825a5484b..3fc537baef90 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -102,8 +102,7 @@ def __array__(self, dtype=None): exp_p = exp(1j * phi) return numpy.array([[cos, -1j * exp_m * sin], [-1j * exp_p * sin, cos]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): theta, phi = self.params return RGate(exponent * theta, phi) diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 6ef963fa6bdf..3483d5ebc956 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -126,8 +126,7 @@ def __array__(self, dtype=None): sin = math.sin(self.params[0] / 2) return numpy.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RXGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py index ddfe312291fd..03e9d22dcc24 100644 --- a/qiskit/circuit/library/standard_gates/rxx.py +++ b/qiskit/circuit/library/standard_gates/rxx.py @@ -132,8 +132,7 @@ def __array__(self, dtype=None): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RXXGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 41e3b2598d15..b902887ee0e0 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -125,8 +125,7 @@ def __array__(self, dtype=None): sin = math.sin(self.params[0] / 2) return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RYGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py index 059f5c117959..50ce9b0c4f73 100644 --- a/qiskit/circuit/library/standard_gates/ryy.py +++ b/qiskit/circuit/library/standard_gates/ryy.py @@ -132,8 +132,7 @@ def __array__(self, dtype=None): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RYYGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index fcd16c8240c2..c7311b4a6e59 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -137,8 +137,7 @@ def __array__(self, dtype=None): ilam2 = 0.5j * float(self.params[0]) return np.array([[exp(-ilam2), 0], [0, exp(ilam2)]], dtype=dtype) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RZGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py index 42ba9158db0f..d59676663da1 100644 --- a/qiskit/circuit/library/standard_gates/rzx.py +++ b/qiskit/circuit/library/standard_gates/rzx.py @@ -178,8 +178,7 @@ def __array__(self, dtype=None): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RZXGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py index 01cd1da199ea..3a00fb7b7395 100644 --- a/qiskit/circuit/library/standard_gates/rzz.py +++ b/qiskit/circuit/library/standard_gates/rzz.py @@ -145,8 +145,7 @@ def __array__(self, dtype=None): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): (theta,) = self.params return RZZGate(exponent * theta) diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index 5014e948f888..6fde1c6544e5 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -94,8 +94,7 @@ def inverse(self, annotated: bool = False): """ return SdgGate() - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): from .p import PhaseGate return PhaseGate(0.5 * numpy.pi * exponent) @@ -172,8 +171,7 @@ def inverse(self, annotated: bool = False): """ return SGate() - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): from .p import PhaseGate return PhaseGate(-0.5 * numpy.pi * exponent) @@ -259,8 +257,7 @@ def inverse(self, annotated: bool = False): """ return CSdgGate(ctrl_state=self.ctrl_state) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): from .p import CPhaseGate return CPhaseGate(0.5 * numpy.pi * exponent) @@ -345,8 +342,7 @@ def inverse(self, annotated: bool = False): """ return CSGate(ctrl_state=self.ctrl_state) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): from .p import CPhaseGate return CPhaseGate(-0.5 * numpy.pi * exponent) diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py index 3ae47035e7b0..87a38d9d44c1 100644 --- a/qiskit/circuit/library/standard_gates/t.py +++ b/qiskit/circuit/library/standard_gates/t.py @@ -92,8 +92,7 @@ def inverse(self, annotated: bool = False): """ return TdgGate() - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): return PhaseGate(0.25 * numpy.pi * exponent) def __eq__(self, other): @@ -168,8 +167,7 @@ def inverse(self, annotated: bool = False): """ return TGate() - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): return PhaseGate(-0.25 * numpy.pi * exponent) def __eq__(self, other): diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py index 2bdb32ad3c2b..387a23ad058a 100644 --- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py @@ -184,8 +184,7 @@ def __array__(self, dtype=complex): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): theta, beta = self.params return XXMinusYYGate(exponent * theta, beta) diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index 5b53d9861ebb..b69ba49de30d 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -185,8 +185,7 @@ def __array__(self, dtype=complex): dtype=dtype, ) - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): theta, beta = self.params return XXPlusYYGate(exponent * theta, beta) diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index ef078ddb1aa7..2b69595936d8 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -140,8 +140,7 @@ def inverse(self, annotated: bool = False): """ return ZGate() # self-inverse - def power(self, exponent: float): - """Raise gate to a power.""" + def power(self, exponent: float, annotated: bool = False): return PhaseGate(numpy.pi * exponent) def __eq__(self, other): diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 30afc47cb618..f25dca4b03b4 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -777,26 +777,38 @@ def repeat(self, reps: int) -> "QuantumCircuit": return repeated_circ - def power(self, power: float, matrix_power: bool = False) -> "QuantumCircuit": + def power( + self, power: float, matrix_power: bool = False, annotated: bool = False + ) -> "QuantumCircuit": """Raise this circuit to the power of ``power``. - If ``power`` is a positive integer and ``matrix_power`` is ``False``, this implementation - defaults to calling ``repeat``. Otherwise, if the circuit is unitary, the matrix is - computed to calculate the matrix power. + If ``power`` is a positive integer and both ``matrix_power`` and ``annotated`` + are ``False``, this implementation defaults to calling ``repeat``. Otherwise, + the circuit is converted into a gate, and a new circuit, containing this gate + raised to the given power, is returned. The gate raised to the given power is + implemented either as a unitary gate if ``annotated`` is ``False`` or as an + annotated operation if ``annotated`` is ``True``. Args: power (float): The power to raise this circuit to. - matrix_power (bool): If True, the circuit is converted to a matrix and then the - matrix power is computed. If False, and ``power`` is a positive integer, - the implementation defaults to ``repeat``. + matrix_power (bool): indicates whether the inner power gate can be implemented + as a unitary gate. + annotated (bool): indicates whether the inner power gate can be implemented + as an annotated operation. Raises: - CircuitError: If the circuit needs to be converted to a gate but it is not unitary. + CircuitError: If the circuit needs to be converted to a unitary gate, but is + not unitary. Returns: QuantumCircuit: A circuit implementing this circuit raised to the power of ``power``. """ - if power >= 0 and isinstance(power, (int, np.integer)) and not matrix_power: + if ( + power >= 0 + and isinstance(power, (int, np.integer)) + and not matrix_power + and not annotated + ): return self.repeat(power) # attempt conversion to gate @@ -812,12 +824,12 @@ def power(self, power: float, matrix_power: bool = False) -> "QuantumCircuit": except QiskitError as ex: raise CircuitError( "The circuit contains non-unitary operations and cannot be " - "controlled. Note that no qiskit.circuit.Instruction objects may " - "be in the circuit for this operation." + "raised to a power. Note that no qiskit.circuit.Instruction " + "objects may be in the circuit for this operation." ) from ex power_circuit = QuantumCircuit(self.qubits, self.clbits, *self.qregs, *self.cregs) - power_circuit.append(gate.power(power), list(range(gate.num_qubits))) + power_circuit.append(gate.power(power, annotated=annotated), list(range(gate.num_qubits))) return power_circuit def control( diff --git a/releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml b/releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml new file mode 100644 index 000000000000..95537833b6d4 --- /dev/null +++ b/releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + The methods :meth:`~qiskit.circuit.QuantumCircuit.power`, + :meth:`~qiskit.circuit.Gate.power`, as well as the similar methods + of subclasses of :class:`~qiskit.circuit.Gate` + (such as of :class:`~qiskit.circuit.library.SGate`) all have an additional + argument ``annotated``. + The default value of ``False`` corresponds to the existing behavior. + Furthermore, for standard gates with an explicitly defined ``power`` method, + the argument ``annotated`` has no effect, for example both + ``SGate().power(1.5, annotated=False)`` and ``SGate().power(1.5, annotated=True)`` + return a ``PhaseGate``. + The difference manifests for gates without an explicitly defined + power method. The value of ``False`` returns a + :class:`~.library.UnitaryGate`, just as before, while the value of ``True`` + returns an :class:`~.AnnotatedOperation` that represents the instruction + modified with the "power modifier". diff --git a/test/python/circuit/test_annotated_operation.py b/test/python/circuit/test_annotated_operation.py index e26d3f845e79..f4228fc0485f 100644 --- a/test/python/circuit/test_annotated_operation.py +++ b/test/python/circuit/test_annotated_operation.py @@ -27,7 +27,7 @@ from test import QiskitTestCase # pylint: disable=wrong-import-order -class TestAnnotatedOperationlass(QiskitTestCase): +class TestAnnotatedOperationClass(QiskitTestCase): """Testing qiskit.circuit.AnnotatedOperation""" def test_create_gate_with_modifier(self): diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 61108ed03b2e..483224196798 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -19,7 +19,7 @@ from ddt import data, ddt from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister -from qiskit.circuit import Gate, Instruction, Measure, Parameter, Barrier +from qiskit.circuit import Gate, Instruction, Measure, Parameter, Barrier, AnnotatedOperation from qiskit.circuit.bit import Bit from qiskit.circuit.classical import expr, types from qiskit.circuit.classicalregister import Clbit @@ -1013,6 +1013,21 @@ def test_power(self): with self.subTest("negative power"): self.assertEqual(qc.power(-2).data[0].operation, gate.power(-2)) + with self.subTest("integer circuit power via annotation"): + power_qc = qc.power(4, annotated=True) + self.assertIsInstance(power_qc[0].operation, AnnotatedOperation) + self.assertEqual(Operator(power_qc), Operator(qc).power(4)) + + with self.subTest("float circuit power via annotation"): + power_qc = qc.power(1.5, annotated=True) + self.assertIsInstance(power_qc[0].operation, AnnotatedOperation) + self.assertEqual(Operator(power_qc), Operator(qc).power(1.5)) + + with self.subTest("negative circuit power via annotation"): + power_qc = qc.power(-2, annotated=True) + self.assertIsInstance(power_qc[0].operation, AnnotatedOperation) + self.assertEqual(Operator(power_qc), Operator(qc).power(-2)) + def test_power_parameterized_circuit(self): """Test taking a parameterized circuit to a power.""" theta = Parameter("th") diff --git a/test/python/circuit/test_gate_power.py b/test/python/circuit/test_gate_power.py index 2dea1242ecaf..918a736c415d 100644 --- a/test/python/circuit/test_gate_power.py +++ b/test/python/circuit/test_gate_power.py @@ -20,7 +20,7 @@ import scipy.linalg from ddt import data, ddt, unpack -from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit import Gate, QuantumCircuit, AnnotatedOperation from qiskit.circuit.library import ( CPhaseGate, CSdgGate, @@ -45,6 +45,7 @@ XXPlusYYGate, ZGate, iSwapGate, + SwapGate, ) from qiskit.quantum_info.operators import Operator from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -260,5 +261,28 @@ def test_efficient_gate_powering(self, gate: Gate, output_gate_type: Type[Gate]) np.testing.assert_allclose(np.array(result), expected, atol=1e-8) +@ddt +class TestPowerAnnotatedGate(QiskitTestCase): + """Test raising gates to power using ``annotated`` argument.""" + + def test_s_gate(self): + """Test raising SGate to a power. Since SGate has an efficient ``power`` + method, the result should not be an annotated operation. + """ + result = SGate().power(1.5, annotated=True) + self.assertNotIsInstance(result, AnnotatedOperation) + + def test_swap_gate(self): + """Test raising SwapGate to a power using different methods.""" + # SwapGate has no native power method. + result1 = SwapGate().power(1.5, annotated=True) + self.assertIsInstance(result1, AnnotatedOperation) + + result2 = SwapGate().power(1.5, annotated=False) + self.assertIsInstance(result2, UnitaryGate) + + self.assertEqual(Operator(result1), Operator(result2)) + + if __name__ == "__main__": unittest.main() From c9f6a10935e8ef7d4482f7154ac530727e99581f Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 23 Apr 2024 21:45:56 +0100 Subject: [PATCH 018/179] Add fast-path construction to `NLocal` (#12099) This class is hard to make the most efficient, due to how abstract the base class is, with many different options and many private methods that subclasses override. Still, in cases where, _at the point of build_, we can detect that rotation or entanglement layers are simple applications of a single standard-library gate, we can skip the entire `copy` + `assign` + `compose` pipeline and simply construct the gates in-place with the correct parameters. This skips huge tracts of overhead that using the high-level, abstract interfaces (which are somewhat necessarily more optimised to work with large operands) in tight inner loops adds, culminating in about a 10x improvement in build time. `NLocal` is so abstract that it's hard to hit similar performance to an idiomatic direct construction of the relevant structure, but to be fair, the concept of a circuit library is not necessarily to make the absolute fastest constructors for tight loops, but to make it much simpler to just get a circuit that works as intended. --- qiskit/circuit/library/n_local/n_local.py | 149 ++++++++++++------ .../notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml | 9 ++ 2 files changed, 109 insertions(+), 49 deletions(-) create mode 100644 releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index e4f5b9be4bf4..430edfd94f39 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -13,16 +13,24 @@ """The n-local circuit class.""" from __future__ import annotations + +import collections +import itertools import typing from collections.abc import Callable, Mapping, Sequence -from itertools import combinations - import numpy from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit import Instruction, Parameter, ParameterVector, ParameterExpression +from qiskit.circuit import ( + Instruction, + Parameter, + ParameterVector, + ParameterExpression, + CircuitInstruction, +) from qiskit.exceptions import QiskitError +from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from ..blueprintcircuit import BlueprintCircuit @@ -154,6 +162,17 @@ def __init__( self._bounds: list[tuple[float | None, float | None]] | None = None self._flatten = flatten + # During the build, if a subclass hasn't overridden our parametrisation methods, we can use + # a newer fast-path method to parametrise the rotation and entanglement blocks if internally + # those are just simple stdlib gates that have been promoted to circuits. We don't + # precalculate the fast-path layers themselves because there's far too much that can be + # overridden between object construction and build, and far too many subclasses of `NLocal` + # that override bits and bobs of the internal private methods, so it'd be too hard to keep + # everything in sync. + self._allow_fast_path_parametrization = ( + getattr(self._parameter_generator, "__func__", None) is NLocal._parameter_generator + ) + if int(reps) != reps: raise TypeError("The value of reps should be int") @@ -779,13 +798,10 @@ def add_layer( else: entangler_map = entanglement - layer = QuantumCircuit(self.num_qubits) for i in entangler_map: params = self.ordered_parameters[-len(get_parameters(block)) :] parameterized_block = self._parameterize_block(block, params=params) - layer.compose(parameterized_block, i, inplace=True) - - self.compose(layer, inplace=True) + self.compose(parameterized_block, i, inplace=True, copy=False) else: # cannot prepend a block currently, just rebuild self._invalidate() @@ -843,52 +859,65 @@ def _build_rotation_layer(self, circuit, param_iter, i): """Build a rotation layer.""" # if the unentangled qubits are skipped, compute the set of qubits that are not entangled if self._skip_unentangled_qubits: - unentangled_qubits = self.get_unentangled_qubits() + skipped_qubits = self.get_unentangled_qubits() + else: + skipped_qubits = set() + + target_qubits = circuit.qubits # iterate over all rotation blocks for j, block in enumerate(self.rotation_blocks): - # create a new layer - layer = QuantumCircuit(*self.qregs) - - # we apply the rotation gates stacked on top of each other, i.e. - # if we have 4 qubits and a rotation block of width 2, we apply two instances - block_indices = [ - list(range(k * block.num_qubits, (k + 1) * block.num_qubits)) - for k in range(self.num_qubits // block.num_qubits) - ] - - # if unentangled qubits should not be acted on, remove all operations that - # touch an unentangled qubit - if self._skip_unentangled_qubits: + skipped_blocks = {qubit // block.num_qubits for qubit in skipped_qubits} + if ( + self._allow_fast_path_parametrization + and (simple_block := _stdlib_gate_from_simple_block(block)) is not None + ): + all_qubits = ( + tuple(target_qubits[k * block.num_qubits : (k + 1) * block.num_qubits]) + for k in range(self.num_qubits // block.num_qubits) + if k not in skipped_blocks + ) + for qubits in all_qubits: + instr = CircuitInstruction( + simple_block.gate(*itertools.islice(param_iter, simple_block.num_params)), + qubits, + ) + circuit._append(instr) + else: block_indices = [ - indices - for indices in block_indices - if set(indices).isdisjoint(unentangled_qubits) + list(range(k * block.num_qubits, (k + 1) * block.num_qubits)) + for k in range(self.num_qubits // block.num_qubits) + if k not in skipped_blocks ] - - # apply the operations in the layer - for indices in block_indices: - parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) - layer.compose(parameterized_block, indices, inplace=True) - - # add the layer to the circuit - circuit.compose(layer, inplace=True) + # apply the operations in the layer + for indices in block_indices: + parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) + circuit.compose(parameterized_block, indices, inplace=True, copy=False) def _build_entanglement_layer(self, circuit, param_iter, i): """Build an entanglement layer.""" # iterate over all entanglement blocks + target_qubits = circuit.qubits for j, block in enumerate(self.entanglement_blocks): - # create a new layer and get the entangler map for this block - layer = QuantumCircuit(*self.qregs) entangler_map = self.get_entangler_map(i, j, block.num_qubits) - - # apply the operations in the layer - for indices in entangler_map: - parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) - layer.compose(parameterized_block, indices, inplace=True) - - # add the layer to the circuit - circuit.compose(layer, inplace=True) + if ( + self._allow_fast_path_parametrization + and (simple_block := _stdlib_gate_from_simple_block(block)) is not None + ): + for indices in entangler_map: + # It's actually nontrivially faster to use a listcomp and pass that to `tuple` + # than to pass a generator expression directly. + # pylint: disable=consider-using-generator + instr = CircuitInstruction( + simple_block.gate(*itertools.islice(param_iter, simple_block.num_params)), + tuple([target_qubits[i] for i in indices]), + ) + circuit._append(instr) + else: + # apply the operations in the layer + for indices in entangler_map: + parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) + circuit.compose(parameterized_block, indices, inplace=True, copy=False) def _build_additional_layers(self, circuit, which): if which == "appended": @@ -901,13 +930,10 @@ def _build_additional_layers(self, circuit, which): raise ValueError("`which` must be either `appended` or `prepended`.") for block, ent in zip(blocks, entanglements): - layer = QuantumCircuit(*self.qregs) if isinstance(ent, str): ent = get_entangler_map(block.num_qubits, self.num_qubits, ent) for indices in ent: - layer.compose(block, indices, inplace=True) - - circuit.compose(layer, inplace=True) + circuit.compose(block, indices, inplace=True, copy=False) def _build(self) -> None: """If not already built, build the circuit.""" @@ -926,7 +952,7 @@ def _build(self) -> None: # use the initial state as starting circuit, if it is set if self.initial_state: - circuit.compose(self.initial_state.copy(), inplace=True) + circuit.compose(self.initial_state.copy(), inplace=True, copy=False) param_iter = iter(self.ordered_parameters) @@ -972,7 +998,7 @@ def _build(self) -> None: except QiskitError: block = circuit.to_instruction() - self.append(block, self.qubits) + self.append(block, self.qubits, copy=False) # pylint: disable=unused-argument def _parameter_generator(self, rep: int, block: int, indices: list[int]) -> Parameter | None: @@ -1023,7 +1049,7 @@ def get_entangler_map( raise ValueError("Pairwise entanglement is not defined for blocks with more than 2 qubits.") if entanglement == "full": - return list(combinations(list(range(n)), m)) + return list(itertools.combinations(list(range(n)), m)) elif entanglement == "reverse_linear": # reverse linear connectivity. In the case of m=2 and the entanglement_block='cx' # then it's equivalent to 'full' entanglement @@ -1057,3 +1083,28 @@ def get_entangler_map( else: raise ValueError(f"Unsupported entanglement type: {entanglement}") + + +_StdlibGateResult = collections.namedtuple("_StdlibGateResult", ("gate", "num_params")) +_STANDARD_GATE_MAPPING = get_standard_gate_name_mapping() + + +def _stdlib_gate_from_simple_block(block: QuantumCircuit) -> _StdlibGateResult | None: + if block.global_phase != 0.0 or len(block) != 1: + return None + instruction = block.data[0] + # If the single instruction isn't a standard-library gate that spans the full width of the block + # in the correct order, we're not simple. If the gate isn't fully parametrised with pure, + # unique `Parameter` instances (expressions are too complex) that are in order, we're not + # simple. + if ( + instruction.clbits + or tuple(instruction.qubits) != tuple(block.qubits) + or ( + getattr(_STANDARD_GATE_MAPPING.get(instruction.operation.name), "base_class", None) + is not instruction.operation.base_class + ) + or tuple(instruction.operation.params) != tuple(block.parameters) + ): + return None + return _StdlibGateResult(instruction.operation.base_class, len(instruction.operation.params)) diff --git a/releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml b/releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml new file mode 100644 index 000000000000..9f8afa4e859a --- /dev/null +++ b/releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml @@ -0,0 +1,9 @@ +--- +features_circuits: + - | + The construction performance of :class:`.NLocal` and its derived circuit-library subclasses + (e.g. :class:`.EfficientSU2` and :class:`.RealAmplitudes`) has significantly improved, when the + rotation and/or entanglement subblocks are simple applications of a single Qiskit + standard-library gate. Since these circuits are constructed lazily, you might not see the + improvement immediately on instantiation of the class, but instead on first access to its + internal structure. Performance improvements are on the order of ten times faster. From a827aaa2e0a6371f29c84752304031d965f7dbaa Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 24 Apr 2024 15:55:29 +0200 Subject: [PATCH 019/179] Rename ``show_idle`` and ``show_barrier`` to ``idle_wires`` and ``plot_barriers`` (#11878) * deprecate args show_idle and show_barrier * reno * Update qiskit/visualization/timeline/interface.py Co-authored-by: Matthew Treinish * pending=True --------- Co-authored-by: Matthew Treinish --- qiskit/pulse/schedule.py | 17 +++++++----- qiskit/visualization/pulse_v2/interface.py | 11 +++++--- qiskit/visualization/timeline/interface.py | 26 +++++++++++++------ ...dle_and_show_barrier-6e77e1f9d6f55599.yaml | 6 +++++ 4 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 2a32b06a4780..a4d1ad844e5f 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -53,6 +53,7 @@ from qiskit.pulse.utils import instruction_duration_validation from qiskit.pulse.reference_manager import ReferenceManager from qiskit.utils.multiprocessing import is_main_process +from qiskit.utils import deprecate_arg Interval = Tuple[int, int] @@ -1643,6 +1644,7 @@ def wrapper(*args, **kwargs): return decorator +@deprecate_arg("show_barriers", new_alias="plot_barriers", since="1.1.0", pending=True) @_common_method(Schedule, ScheduleBlock) def draw( self, @@ -1654,9 +1656,10 @@ def draw( show_snapshot: bool = True, show_framechange: bool = True, show_waveform_info: bool = True, - show_barrier: bool = True, + plot_barrier: bool = True, plotter: str = "mpl2d", axis: Any | None = None, + show_barrier: bool = True, ): """Plot the schedule. @@ -1668,16 +1671,16 @@ def draw( preset stylesheets. backend (Optional[BaseBackend]): Backend object to play the input pulse program. If provided, the plotter may use to make the visualization hardware aware. - time_range: Set horizontal axis limit. Tuple `(tmin, tmax)`. - time_unit: The unit of specified time range either `dt` or `ns`. - The unit of `ns` is available only when `backend` object is provided. + time_range: Set horizontal axis limit. Tuple ``(tmin, tmax)``. + time_unit: The unit of specified time range either ``dt`` or ``ns``. + The unit of `ns` is available only when ``backend`` object is provided. disable_channels: A control property to show specific pulse channel. Pulse channel instances provided as a list are not shown in the output image. show_snapshot: Show snapshot instructions. show_framechange: Show frame change instructions. The frame change represents instructions that modulate phase or frequency of pulse channels. show_waveform_info: Show additional information about waveforms such as their name. - show_barrier: Show barrier lines. + plot_barrier: Show barrier lines. plotter: Name of plotter API to generate an output image. One of following APIs should be specified:: @@ -1690,6 +1693,7 @@ def draw( the plotters use a given ``axis`` instead of internally initializing a figure object. This object format depends on the plotter. See plotter argument for details. + show_barrier: DEPRECATED. Show barrier lines. Returns: Visualization output data. @@ -1699,6 +1703,7 @@ def draw( # pylint: disable=cyclic-import from qiskit.visualization import pulse_drawer + del show_barrier return pulse_drawer( program=self, style=style, @@ -1709,7 +1714,7 @@ def draw( show_snapshot=show_snapshot, show_framechange=show_framechange, show_waveform_info=show_waveform_info, - show_barrier=show_barrier, + plot_barrier=plot_barrier, plotter=plotter, axis=axis, ) diff --git a/qiskit/visualization/pulse_v2/interface.py b/qiskit/visualization/pulse_v2/interface.py index 90e4fdfa60e6..75370905d8d9 100644 --- a/qiskit/visualization/pulse_v2/interface.py +++ b/qiskit/visualization/pulse_v2/interface.py @@ -27,8 +27,10 @@ from qiskit.visualization.exceptions import VisualizationError from qiskit.visualization.pulse_v2 import core, device_info, stylesheet, types from qiskit.exceptions import MissingOptionalLibraryError +from qiskit.utils import deprecate_arg +@deprecate_arg("show_barriers", new_alias="plot_barriers", since="1.1.0", pending=True) def draw( program: Union[Waveform, SymbolicPulse, Schedule, ScheduleBlock], style: Optional[Dict[str, Any]] = None, @@ -39,9 +41,10 @@ def draw( show_snapshot: bool = True, show_framechange: bool = True, show_waveform_info: bool = True, - show_barrier: bool = True, + plot_barrier: bool = True, plotter: str = types.Plotter.Mpl2D.value, axis: Optional[Any] = None, + show_barrier: bool = True, ): """Generate visualization data for pulse programs. @@ -66,7 +69,7 @@ def draw( instructions that modulate phase or frequency of pulse channels. show_waveform_info: Show waveform annotations, i.e. name, of waveforms. Set ``True`` to show additional information about waveforms. - show_barrier: Show barrier lines. + plot_barrier: Show barrier lines. plotter: Name of plotter API to generate an output image. One of following APIs should be specified:: @@ -79,6 +82,7 @@ def draw( the plotters use a given ``axis`` instead of internally initializing a figure object. This object format depends on the plotter. See plotter argument for details. + show_barrier: DEPRECATED. Show barrier lines. Returns: Visualization output data. @@ -379,6 +383,7 @@ def draw( MissingOptionalLibraryError: When required visualization package is not installed. VisualizationError: When invalid plotter API or invalid time range is specified. """ + del show_barrier temp_style = stylesheet.QiskitPulseStyle() temp_style.update(style or stylesheet.IQXStandard()) @@ -425,7 +430,7 @@ def draw( canvas.set_disable_type(types.LabelType.PULSE_NAME, remove=True) # show barrier - if not show_barrier: + if not plot_barrier: canvas.set_disable_type(types.LineType.BARRIER, remove=True) canvas.update() diff --git a/qiskit/visualization/timeline/interface.py b/qiskit/visualization/timeline/interface.py index ef0f072c3882..f5bb7e16f8d3 100644 --- a/qiskit/visualization/timeline/interface.py +++ b/qiskit/visualization/timeline/interface.py @@ -25,21 +25,27 @@ from qiskit.exceptions import MissingOptionalLibraryError from qiskit.visualization.exceptions import VisualizationError from qiskit.visualization.timeline import types, core, stylesheet +from qiskit.utils import deprecate_arg +@deprecate_arg("show_idle", new_alias="idle_wires", since="1.1.0", pending=True) +@deprecate_arg("show_barriers", new_alias="plot_barriers", since="1.1.0", pending=True) def draw( program: circuit.QuantumCircuit, style: Optional[Dict[str, Any]] = None, time_range: Tuple[int, int] = None, disable_bits: List[types.Bits] = None, show_clbits: Optional[bool] = None, - show_idle: Optional[bool] = None, - show_barriers: Optional[bool] = None, + idle_wires: Optional[bool] = None, + plot_barriers: Optional[bool] = None, show_delays: Optional[bool] = None, show_labels: bool = True, plotter: Optional[str] = types.Plotter.MPL.value, axis: Optional[Any] = None, filename: Optional[str] = None, + *, + show_idle: Optional[bool] = None, + show_barriers: Optional[bool] = None, ): r"""Generate visualization data for scheduled circuit programs. @@ -55,9 +61,9 @@ def draw( disable_bits: List of qubits of classical bits not shown in the output image. show_clbits: A control property to show classical bits. Set `True` to show classical bits. - show_idle: A control property to show idle timeline. + idle_wires: A control property to show idle timeline. Set `True` to show timeline without gates. - show_barriers: A control property to show barrier instructions. + plot_barriers: A control property to show barrier instructions. Set `True` to show barrier instructions. show_delays: A control property to show delay instructions. Set `True` to show delay instructions. @@ -75,6 +81,8 @@ def draw( the plotters uses given `axis` instead of internally initializing a figure object. This object format depends on the plotter. See plotters section for details. filename: If provided the output image is dumped into a file under the filename. + show_idle: DEPRECATED. + show_barriers: DEPRECATED. Returns: Visualization output data. @@ -347,19 +355,21 @@ def draw( This feature enables you to control the most of appearance of the output image without modifying the codebase of the scheduled circuit drawer. """ + del show_idle + del show_barriers # update stylesheet temp_style = stylesheet.QiskitTimelineStyle() temp_style.update(style or stylesheet.IQXStandard()) # update control properties - if show_idle is not None: - temp_style["formatter.control.show_idle"] = show_idle + if idle_wires is not None: + temp_style["formatter.control.show_idle"] = idle_wires if show_clbits is not None: temp_style["formatter.control.show_clbits"] = show_clbits - if show_barriers is not None: - temp_style["formatter.control.show_barriers"] = show_barriers + if plot_barriers is not None: + temp_style["formatter.control.show_barriers"] = plot_barriers if show_delays is not None: temp_style["formatter.control.show_delays"] = show_delays diff --git a/releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml b/releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml new file mode 100644 index 000000000000..ac0994b9d3a9 --- /dev/null +++ b/releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml @@ -0,0 +1,6 @@ +--- +deprecations: + - | + The parameters ``show_idle`` and ``show_barrier`` in the timeline drawers had been replaced by ``idle_wires`` and ``plot_barriers`` + respectively to match the circuit drawer parameters. Their previous names are now deprecated and will be removed in the next major + release. The new parameters are fully equivalent. From 3fe2e239002d1ab7d5959e2c8dbfdfc8be9799ed Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Wed, 24 Apr 2024 11:13:44 -0400 Subject: [PATCH 020/179] removing modified-iterating-list from lint exclusions and updates (#12294) --- pyproject.toml | 1 - .../passes/optimization/template_matching/forward_match.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0d99f9256df3..7828bc13f12e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -221,7 +221,6 @@ disable = [ "consider-using-dict-items", "consider-using-enumerate", "consider-using-f-string", - "modified-iterating-list", "nested-min-max", "no-member", "no-value-for-parameter", diff --git a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py index decc24453b06..627db502d33e 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py +++ b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py @@ -148,9 +148,7 @@ def _find_forward_candidates(self, node_id_t): if self.template_dag_dep.direct_successors(node_id_t): maximal_index = self.template_dag_dep.direct_successors(node_id_t)[-1] - for elem in pred: - if elem > maximal_index: - pred.remove(elem) + pred = [elem for elem in pred if elem <= maximal_index] block = [] for node_id in pred: From 607bee8c8edc13c72f82eb807c7f3b5639d451af Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Thu, 25 Apr 2024 02:06:32 -0400 Subject: [PATCH 021/179] Treat Reset like Measure in ConstrainedReschedule (#11756) * fix mid-circuit measure problem * Update qiskit/transpiler/passes/scheduling/base_scheduler.py Co-authored-by: Naoki Kanazawa * Update releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml Co-authored-by: Naoki Kanazawa * Update qiskit/transpiler/passes/scheduling/base_scheduler.py Co-authored-by: Naoki Kanazawa * Move runtime warning about reset and measurement durations outside of calibration if statement * Replace raise with warn * Switch import orders --------- Co-authored-by: Naoki Kanazawa --- .../scheduling/alignments/reschedule.py | 7 +++--- .../passes/scheduling/base_scheduler.py | 23 +++++++++++++++++-- ...d-scheduler-warnings-da6968a39fd8e6e7.yaml | 11 +++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml diff --git a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py index 618186a34f9c..5cab7028745b 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py +++ b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py @@ -17,6 +17,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.delay import Delay from qiskit.circuit.measure import Measure +from qiskit.circuit.reset import Reset from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGOutNode from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError @@ -121,7 +122,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): if isinstance(node.op, Gate): alignment = self.pulse_align - elif isinstance(node.op, Measure): + elif isinstance(node.op, (Measure, Reset)): alignment = self.acquire_align elif isinstance(node.op, Delay) or getattr(node.op, "_directive", False): # Directive or delay. These can start at arbitrary time. @@ -143,7 +144,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): # Compute shifted t1 of this node separately for qreg and creg new_t1q = this_t0 + node.op.duration this_qubits = set(node.qargs) - if isinstance(node.op, Measure): + if isinstance(node.op, (Measure, Reset)): # creg access ends at the end of instruction new_t1c = new_t1q this_clbits = set(node.cargs) @@ -161,7 +162,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): # Compute next node start time separately for qreg and creg next_t0q = node_start_time[next_node] next_qubits = set(next_node.qargs) - if isinstance(next_node.op, Measure): + if isinstance(next_node.op, (Measure, Reset)): # creg access starts after write latency next_t0c = next_t0q + clbit_write_latency next_clbits = set(next_node.cargs) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 78e2660e505d..4085844a4709 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -11,11 +11,13 @@ # that they have been altered from the originals. """Base circuit scheduling pass.""" +import warnings + from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion -from qiskit.dagcircuit import DAGOpNode, DAGCircuit -from qiskit.circuit import Delay, Gate +from qiskit.dagcircuit import DAGOpNode, DAGCircuit, DAGOutNode +from qiskit.circuit import Delay, Gate, Measure, Reset from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target @@ -269,6 +271,23 @@ def _get_node_duration( else: duration = node.op.duration + if isinstance(node.op, Reset): + warnings.warn( + "Qiskit scheduler assumes Reset works similarly to Measure instruction. " + "Actual behavior depends on the control system of your quantum backend. " + "Your backend may provide a plugin scheduler pass." + ) + elif isinstance(node.op, Measure): + is_mid_circuit = not any( + isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) + ) + if is_mid_circuit: + warnings.warn( + "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " + "Actual backend may apply custom scheduling. " + "Your backend may provide a plugin scheduler pass." + ) + if isinstance(duration, ParameterExpression): raise TranspilerError( f"Parameterized duration ({duration}) " diff --git a/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml new file mode 100644 index 000000000000..be2c94140300 --- /dev/null +++ b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml @@ -0,0 +1,11 @@ +fixes: + - | + Fixed an issue where the :class:`.ConstrainedReschedule` transpiler pass would previously error + if the circuit contained a :class:`~.circuit.Reset` instruction. This has been corrected so that the + pass no longer errors, however an actual hardware may behave differently from + what Qiskit scheduler assumes especially for + mid-circuit measurements and resets. + Qiskit scheduler raises ``RuntimeWarning`` if + it encounters circuit containing either. + Fixed `#10354 `__ + From c29fae7411f4d0da8117e65deae2b2f5b58b56b7 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:46:22 +0400 Subject: [PATCH 022/179] Improve performance of BackendSamplerV2 and EstimatorV2 (#12291) * consolidate circuits with the same number of shots * update BackendEstimatorV2 * add job size limit tests * Apply suggestions from code review Co-authored-by: Ian Hincks * fix errors * refactor * revise comments * reno * fix an error --------- Co-authored-by: Ian Hincks --- qiskit/primitives/backend_estimator_v2.py | 160 +++++++++++++++--- qiskit/primitives/backend_sampler_v2.py | 98 ++++++++--- ...imitives-performance-1409b08ccc2a5ce9.yaml | 6 + .../primitives/test_backend_estimator_v2.py | 56 +++++- .../primitives/test_backend_sampler_v2.py | 64 ++++++- 5 files changed, 341 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml diff --git a/qiskit/primitives/backend_estimator_v2.py b/qiskit/primitives/backend_estimator_v2.py index 525b223b9505..9afc6d892f3d 100644 --- a/qiskit/primitives/backend_estimator_v2.py +++ b/qiskit/primitives/backend_estimator_v2.py @@ -14,10 +14,10 @@ from __future__ import annotations +import math from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass -import math import numpy as np @@ -25,6 +25,7 @@ from qiskit.exceptions import QiskitError from qiskit.providers import BackendV1, BackendV2 from qiskit.quantum_info import Pauli, PauliList +from qiskit.result import Counts from qiskit.transpiler import PassManager, PassManagerConfig from qiskit.transpiler.passes import Optimize1qGatesDecomposition @@ -56,6 +57,20 @@ class Options: """ +@dataclass +class _PreprocessedData: + """Internal data structure to store the results of the preprocessing of a pub.""" + + circuits: list[QuantumCircuit] + """The quantum circuits generated by binding parameters of the pub's circuit.""" + + parameter_indices: np.ndarray + """The indices of the pub's bindings array broadcast to the shape of the pub.""" + + observables: np.ndarray + """The pub's observable array broadcast to the shape of the pub.""" + + class BackendEstimatorV2(BaseEstimatorV2): """Evaluates expectation values for provided quantum circuit and observable combinations @@ -144,10 +159,58 @@ def _validate_pubs(self, pubs: list[EstimatorPub]): ) def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: - return PrimitiveResult([self._run_pub(pub) for pub in pubs]) + pub_dict = defaultdict(list) + # consolidate pubs with the same number of shots + for i, pub in enumerate(pubs): + shots = int(math.ceil(1.0 / pub.precision**2)) + pub_dict[shots].append(i) + + results = [None] * len(pubs) + for shots, lst in pub_dict.items(): + # run pubs with the same number of shots at once + pub_results = self._run_pubs([pubs[i] for i in lst], shots) + # reconstruct the result of pubs + for i, pub_result in zip(lst, pub_results): + results[i] = pub_result + return PrimitiveResult(results) + + def _run_pubs(self, pubs: list[EstimatorPub], shots: int) -> list[PubResult]: + """Compute results for pubs that all require the same value of ``shots``.""" + preprocessed_data = [] + flat_circuits = [] + for pub in pubs: + data = self._preprocess_pub(pub) + preprocessed_data.append(data) + flat_circuits.extend(data.circuits) + + run_result, metadata = _run_circuits( + flat_circuits, self._backend, shots=shots, seed_simulator=self._options.seed_simulator + ) + counts = _prepare_counts(run_result) - def _run_pub(self, pub: EstimatorPub) -> PubResult: - shots = math.ceil(1.0 / pub.precision**2) + results = [] + start = 0 + for pub, data in zip(pubs, preprocessed_data): + end = start + len(data.circuits) + expval_map = self._calc_expval_map(counts[start:end], metadata[start:end]) + start = end + results.append(self._postprocess_pub(pub, expval_map, data, shots)) + return results + + def _preprocess_pub(self, pub: EstimatorPub) -> _PreprocessedData: + """Converts a pub into a list of bound circuits necessary to estimate all its observables. + + The circuits contain metadata explaining which bindings array index they are with respect to, + and which measurement basis they are measuring. + + Args: + pub: The pub to preprocess. + + Returns: + The values ``(circuits, bc_param_ind, bc_obs)`` where ``circuits`` are the circuits to + execute on the backend, ``bc_param_ind`` are indices of the pub's bindings array and + ``bc_obs`` is the observables array, both broadcast to the shape of the pub. + """ circuit = pub.circuit observables = pub.observables parameter_values = pub.parameter_values @@ -161,10 +224,29 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult: param_obs_map = defaultdict(set) for index in np.ndindex(*bc_param_ind.shape): param_index = bc_param_ind[index] - param_obs_map[param_index].update(bc_obs[index].keys()) - expval_map = self._calc_expval_paulis(circuit, parameter_values, param_obs_map, shots) + param_obs_map[param_index].update(bc_obs[index]) + + bound_circuits = self._bind_and_add_measurements(circuit, parameter_values, param_obs_map) + return _PreprocessedData(bound_circuits, bc_param_ind, bc_obs) + + def _postprocess_pub( + self, pub: EstimatorPub, expval_map: dict, data: _PreprocessedData, shots: int + ) -> PubResult: + """Computes expectation values (evs) and standard errors (stds). + + The values are stored in arrays broadcast to the shape of the pub. - # calculate expectation values (evs) and standard errors (stds) + Args: + pub: The pub to postprocess. + expval_map: The map + data: The result data of the preprocessing. + shots: The number of shots. + + Returns: + The pub result. + """ + bc_param_ind = data.parameter_indices + bc_obs = data.observables evs = np.zeros_like(bc_param_ind, dtype=float) variances = np.zeros_like(bc_param_ind, dtype=float) for index in np.ndindex(*bc_param_ind.shape): @@ -178,30 +260,55 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult: data_bin = data_bin_cls(evs=evs, stds=stds) return PubResult(data_bin, metadata={"target_precision": pub.precision}) - def _calc_expval_paulis( + def _bind_and_add_measurements( self, circuit: QuantumCircuit, parameter_values: BindingsArray, param_obs_map: dict[tuple[int, ...], set[str]], - shots: int, - ) -> dict[tuple[tuple[int, ...], str], tuple[float, float]]: - # generate circuits + ) -> list[QuantumCircuit]: + """Bind the given circuit against each parameter value set, and add necessary measurements + to each. + + Args: + circuit: The (possibly parametric) circuit of interest. + parameter_values: An array of parameter value sets that can be applied to the circuit. + param_obs_map: A mapping from locations in ``parameter_values`` to a sets of + Pauli terms whose expectation values are required in those locations. + + Returns: + A flat list of circuits sufficient to measure all Pauli terms in the ``param_obs_map`` + values at the corresponding ``parameter_values`` location, where requisite + book-keeping is stored as circuit metadata. + """ circuits = [] for param_index, pauli_strings in param_obs_map.items(): bound_circuit = parameter_values.bind(circuit, param_index) # sort pauli_strings so that the order is deterministic meas_paulis = PauliList(sorted(pauli_strings)) - new_circuits = self._preprocessing(bound_circuit, meas_paulis, param_index) + new_circuits = self._create_measurement_circuits( + bound_circuit, meas_paulis, param_index + ) circuits.extend(new_circuits) + return circuits - # run circuits - result, metadata = _run_circuits( - circuits, self._backend, shots=shots, seed_simulator=self._options.seed_simulator - ) + def _calc_expval_map( + self, + counts: list[Counts], + metadata: dict, + ) -> dict[tuple[tuple[int, ...], str], tuple[float, float]]: + """Computes the map of expectation values. - # postprocessing results + Args: + counts: The counts data. + metadata: The metadata. + + Returns: + The map of expectation values takes a pair of an index of the bindings array and + a pauli string as a key and returns the expectation value of the pauli string + with the the pub's circuit bound against the parameter value set in the index of + the bindings array. + """ expval_map: dict[tuple[tuple[int, ...], str], tuple[float, float]] = {} - counts = _prepare_counts(result) for count, meta in zip(counts, metadata): orig_paulis = meta["orig_paulis"] meas_paulis = meta["meas_paulis"] @@ -211,10 +318,23 @@ def _calc_expval_paulis( expval_map[param_index, pauli.to_label()] = (expval, variance) return expval_map - def _preprocessing( + def _create_measurement_circuits( self, circuit: QuantumCircuit, observable: PauliList, param_index: tuple[int, ...] ) -> list[QuantumCircuit]: - # generate measurement circuits with metadata + """Generate a list of circuits sufficient to estimate each of the given Paulis. + + Paulis are divided into qubitwise-commuting subsets to reduce the total circuit count. + Metadata is attached to circuits in order to remember what each one measures, and + where it belongs in the output. + + Args: + circuit: The circuit of interest. + observable: Which Pauli terms we would like to observe. + param_index: Where to put the data we estimate (only passed to metadata). + + Returns: + A list of circuits sufficient to estimate each of the given Paulis. + """ meas_circuits: list[QuantumCircuit] = [] if self._options.abelian_grouping: for obs in observable.group_commuting(qubit_wise=True): diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py index 23861aa34f45..6a990465c5bd 100644 --- a/qiskit/primitives/backend_sampler_v2.py +++ b/qiskit/primitives/backend_sampler_v2.py @@ -15,6 +15,7 @@ from __future__ import annotations import warnings +from collections import defaultdict from dataclasses import dataclass from typing import Iterable @@ -142,36 +143,76 @@ def _validate_pubs(self, pubs: list[SamplerPub]): ) def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[PubResult]: - results = [self._run_pub(pub) for pub in pubs] + pub_dict = defaultdict(list) + # consolidate pubs with the same number of shots + for i, pub in enumerate(pubs): + pub_dict[pub.shots].append(i) + + results = [None] * len(pubs) + for shots, lst in pub_dict.items(): + # run pubs with the same number of shots at once + pub_results = self._run_pubs([pubs[i] for i in lst], shots) + # reconstruct the result of pubs + for i, pub_result in zip(lst, pub_results): + results[i] = pub_result return PrimitiveResult(results) - def _run_pub(self, pub: SamplerPub) -> PubResult: - meas_info, max_num_bytes = _analyze_circuit(pub.circuit) - bound_circuits = pub.parameter_values.bind_all(pub.circuit) - arrays = { - item.creg_name: np.zeros( - bound_circuits.shape + (pub.shots, item.num_bytes), dtype=np.uint8 - ) - for item in meas_info - } - flatten_circuits = np.ravel(bound_circuits).tolist() - result_memory, _ = _run_circuits( + def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[PubResult]: + """Compute results for pubs that all require the same value of ``shots``.""" + # prepare circuits + bound_circuits = [pub.parameter_values.bind_all(pub.circuit) for pub in pubs] + flatten_circuits = [] + for circuits in bound_circuits: + flatten_circuits.extend(np.ravel(circuits).tolist()) + + # run circuits + results, _ = _run_circuits( flatten_circuits, self._backend, memory=True, - shots=pub.shots, + shots=shots, seed_simulator=self._options.seed_simulator, ) - memory_list = _prepare_memory(result_memory, max_num_bytes) + result_memory = _prepare_memory(results) + + # pack memory to an ndarray of uint8 + results = [] + start = 0 + for pub, bound in zip(pubs, bound_circuits): + meas_info, max_num_bytes = _analyze_circuit(pub.circuit) + end = start + bound.size + results.append( + self._postprocess_pub( + result_memory[start:end], shots, bound.shape, meas_info, max_num_bytes + ) + ) + start = end + + return results - for samples, index in zip(memory_list, np.ndindex(*bound_circuits.shape)): + def _postprocess_pub( + self, + result_memory: list[list[str]], + shots: int, + shape: tuple[int, ...], + meas_info: list[_MeasureInfo], + max_num_bytes: int, + ) -> PubResult: + """Converts the memory data into an array of bit arrays with the shape of the pub.""" + arrays = { + item.creg_name: np.zeros(shape + (shots, item.num_bytes), dtype=np.uint8) + for item in meas_info + } + memory_array = _memory_array(result_memory, max_num_bytes) + + for samples, index in zip(memory_array, np.ndindex(*shape)): for item in meas_info: ary = _samples_to_packed_array(samples, item.num_bits, item.start) arrays[item.creg_name][index] = ary data_bin_cls = make_data_bin( [(item.creg_name, BitArray) for item in meas_info], - shape=bound_circuits.shape, + shape=shape, ) meas = { item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info @@ -181,6 +222,7 @@ def _run_pub(self, pub: SamplerPub) -> PubResult: def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]: + """Analyzes the information for each creg in a circuit.""" meas_info = [] max_num_bits = 0 for creg in circuit.cregs: @@ -202,17 +244,30 @@ def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]: return meas_info, _min_num_bytes(max_num_bits) -def _prepare_memory(results: list[Result], num_bytes: int) -> NDArray[np.uint8]: +def _prepare_memory(results: list[Result]) -> list[list[str]]: + """Joins splitted results if exceeding max_experiments""" lst = [] for res in results: for exp in res.results: if hasattr(exp.data, "memory") and exp.data.memory: - data = b"".join(int(i, 16).to_bytes(num_bytes, "big") for i in exp.data.memory) - data = np.frombuffer(data, dtype=np.uint8).reshape(-1, num_bytes) + lst.append(exp.data.memory) else: # no measure in a circuit - data = np.zeros((exp.shots, num_bytes), dtype=np.uint8) - lst.append(data) + lst.append(["0x0"] * exp.shots) + return lst + + +def _memory_array(results: list[list[str]], num_bytes: int) -> NDArray[np.uint8]: + """Converts the memory data into an array in an unpacked way.""" + lst = [] + for memory in results: + if num_bytes > 0: + data = b"".join(int(i, 16).to_bytes(num_bytes, "big") for i in memory) + data = np.frombuffer(data, dtype=np.uint8).reshape(-1, num_bytes) + else: + # no measure in a circuit + data = np.zeros((len(memory), num_bytes), dtype=np.uint8) + lst.append(data) ary = np.array(lst, copy=False) return np.unpackbits(ary, axis=-1, bitorder="big") @@ -220,6 +275,7 @@ def _prepare_memory(results: list[Result], num_bytes: int) -> NDArray[np.uint8]: def _samples_to_packed_array( samples: NDArray[np.uint8], num_bits: int, start: int ) -> NDArray[np.uint8]: + """Converts an unpacked array of the memory data into a packed array.""" # samples of `Backend.run(memory=True)` will be the order of # clbit_last, ..., clbit_1, clbit_0 # place samples in the order of clbit_start+num_bits-1, ..., clbit_start+1, clbit_start diff --git a/releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml b/releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml new file mode 100644 index 000000000000..883122d6f5b4 --- /dev/null +++ b/releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a performance issue in the :class:`~.BackendSamplerV2` and + :class:`~.BackendEstimatorV2`. + Fixed `#12290 `__ \ No newline at end of file diff --git a/test/python/primitives/test_backend_estimator_v2.py b/test/python/primitives/test_backend_estimator_v2.py index 570b117af583..2af6b15b8777 100644 --- a/test/python/primitives/test_backend_estimator_v2.py +++ b/test/python/primitives/test_backend_estimator_v2.py @@ -16,6 +16,7 @@ import unittest from test import QiskitTestCase, combine +from unittest.mock import patch import numpy as np from ddt import ddt @@ -28,7 +29,7 @@ from qiskit.primitives.containers.observables_array import ObservablesArray from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.basic_provider import BasicSimulator -from qiskit.providers.fake_provider import Fake7QPulseV1 +from qiskit.providers.fake_provider import Fake7QPulseV1, GenericBackendV2 from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.utils import optionals @@ -371,6 +372,22 @@ def test_precision(self, backend, abelian_grouping): result = job.result() np.testing.assert_allclose(result[0].data.evs, [1.5555572817900956], rtol=self._rtol) + @combine(backend=BACKENDS, abelian_grouping=[True, False]) + def test_diff_precision(self, backend, abelian_grouping): + """Test for running different precisions at once""" + estimator = BackendEstimatorV2(backend=backend, options=self._options) + estimator.options.abelian_grouping = abelian_grouping + pm = generate_preset_pass_manager(optimization_level=0, backend=backend) + psi1 = pm.run(self.psi[0]) + hamiltonian1 = self.hamiltonian[0].apply_layout(psi1.layout) + theta1 = self.theta[0] + job = estimator.run( + [(psi1, hamiltonian1, [theta1]), (psi1, hamiltonian1, [theta1], self._precision * 0.8)] + ) + result = job.result() + np.testing.assert_allclose(result[0].data.evs, [1.901141473854881], rtol=self._rtol) + np.testing.assert_allclose(result[1].data.evs, [1.901141473854881], rtol=self._rtol) + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") @combine(abelian_grouping=[True, False]) def test_aer(self, abelian_grouping): @@ -407,6 +424,43 @@ def test_aer(self, abelian_grouping): result[0].data.evs, target[0].data.evs, rtol=self._rtol, atol=1e-1 ) + def test_job_size_limit_backend_v2(self): + """Test BackendEstimatorV2 respects job size limit""" + + class FakeBackendLimitedCircuits(GenericBackendV2): + """Generic backend V2 with job size limit.""" + + @property + def max_circuits(self): + return 1 + + backend = FakeBackendLimitedCircuits(num_qubits=5) + qc = RealAmplitudes(num_qubits=2, reps=2) + # Note: two qubit-wise commuting groups + op = SparsePauliOp.from_list([("IZ", 1), ("XI", 2), ("ZY", -1)]) + k = 5 + param_list = self._rng.random(qc.num_parameters).tolist() + estimator = BackendEstimatorV2(backend=backend) + with patch.object(backend, "run") as run_mock: + estimator.run([(qc, op, param_list)] * k).result() + self.assertEqual(run_mock.call_count, 10) + + def test_job_size_limit_backend_v1(self): + """Test BackendEstimatorV2 respects job size limit""" + backend = Fake7QPulseV1() + config = backend.configuration() + config.max_experiments = 1 + backend._configuration = config + qc = RealAmplitudes(num_qubits=2, reps=2) + # Note: two qubit-wise commuting groups + op = SparsePauliOp.from_list([("IZ", 1), ("XI", 2), ("ZY", -1)]) + k = 5 + param_list = self._rng.random(qc.num_parameters).tolist() + estimator = BackendEstimatorV2(backend=backend) + with patch.object(backend, "run") as run_mock: + estimator.run([(qc, op, param_list)] * k).result() + self.assertEqual(run_mock.call_count, 10) + if __name__ == "__main__": unittest.main() diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py index dcddbf1126ad..64a26471a33e 100644 --- a/test/python/primitives/test_backend_sampler_v2.py +++ b/test/python/primitives/test_backend_sampler_v2.py @@ -33,7 +33,7 @@ from qiskit.providers import JobStatus from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.basic_provider import BasicSimulator -from qiskit.providers.fake_provider import Fake7QPulseV1 +from qiskit.providers.fake_provider import Fake7QPulseV1, GenericBackendV2 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager BACKENDS = [BasicSimulator(), Fake7QPulseV1(), BackendV2Converter(Fake7QPulseV1())] @@ -671,6 +671,68 @@ def test_empty_creg(self, backend): result = sampler.run([qc], shots=self._shots).result() self.assertEqual(result[0].data.c1.array.shape, (self._shots, 0)) + @combine(backend=BACKENDS) + def test_diff_shots(self, backend): + """Test of pubs with different shots""" + pm = generate_preset_pass_manager(optimization_level=0, backend=backend) + + bell, _, target = self._cases[1] + bell = pm.run(bell) + sampler = BackendSamplerV2(backend=backend, options=self._options) + shots2 = self._shots + 2 + target2 = {k: v + 1 for k, v in target.items()} + job = sampler.run([(bell, None, self._shots), (bell, None, shots2)]) + result = job.result() + self.assertEqual(len(result), 2) + self.assertEqual(result[0].data.meas.num_shots, self._shots) + self._assert_allclose(result[0].data.meas, np.array(target)) + self.assertEqual(result[1].data.meas.num_shots, shots2) + self._assert_allclose(result[1].data.meas, np.array(target2)) + + def test_job_size_limit_backend_v2(self): + """Test BackendSamplerV2 respects backend's job size limit.""" + + class FakeBackendLimitedCircuits(GenericBackendV2): + """Generic backend V2 with job size limit.""" + + @property + def max_circuits(self): + return 1 + + qc = QuantumCircuit(1) + qc.measure_all() + qc2 = QuantumCircuit(1) + qc2.x(0) + qc2.measure_all() + sampler = BackendSamplerV2(backend=FakeBackendLimitedCircuits(num_qubits=5)) + result = sampler.run([qc, qc2], shots=self._shots).result() + self.assertIsInstance(result, PrimitiveResult) + self.assertEqual(len(result), 2) + self.assertIsInstance(result[0], PubResult) + self.assertIsInstance(result[1], PubResult) + self._assert_allclose(result[0].data.meas, np.array({0: self._shots})) + self._assert_allclose(result[1].data.meas, np.array({1: self._shots})) + + def test_job_size_limit_backend_v1(self): + """Test BackendSamplerV2 respects backend's job size limit.""" + backend = Fake7QPulseV1() + config = backend.configuration() + config.max_experiments = 1 + backend._configuration = config + qc = QuantumCircuit(1) + qc.measure_all() + qc2 = QuantumCircuit(1) + qc2.x(0) + qc2.measure_all() + sampler = BackendSamplerV2(backend=backend) + result = sampler.run([qc, qc2], shots=self._shots).result() + self.assertIsInstance(result, PrimitiveResult) + self.assertEqual(len(result), 2) + self.assertIsInstance(result[0], PubResult) + self.assertIsInstance(result[1], PubResult) + self._assert_allclose(result[0].data.meas, np.array({0: self._shots})) + self._assert_allclose(result[1].data.meas, np.array({1: self._shots})) + if __name__ == "__main__": unittest.main() From 03b9e0b56b72147ed2d81f2be3840367320a0145 Mon Sep 17 00:00:00 2001 From: Joe Schulte Date: Thu, 25 Apr 2024 08:52:08 -0400 Subject: [PATCH 023/179] Adding note for nested-min-max for linting (#12296) * adding note for nested-min-max for linting * updating note for nested-min-max for linting --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7828bc13f12e..be4cc7f2d9ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -212,6 +212,7 @@ disable = [ "no-else-return", # relax "elif" after a clause with a return "docstring-first-line-empty", # relax docstring style "import-outside-toplevel", "import-error", # overzealous with our optionals/dynamic packages + "nested-min-max", # this gives false equivalencies if implemented for the current lint version # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so, # remove from here and fix the issues. Else, move it above this section and add a comment # with the rationale @@ -221,7 +222,6 @@ disable = [ "consider-using-dict-items", "consider-using-enumerate", "consider-using-f-string", - "nested-min-max", "no-member", "no-value-for-parameter", "not-context-manager", From f15d758838879a67fcc5d7fb7d86b85075c96029 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 25 Apr 2024 12:09:14 -0400 Subject: [PATCH 024/179] Add support for returning a DAGCircuit to TwoQubitBasisDecomposer (#12109) * Add support for returning a DAGCircuit to TwoQubitBasisDecomposer This commit adds a new flag, use_dag, to the constructor for the TwoQubitBasisDecomposer. When set to True, the __call__ method will return a DAGCircuit instead of a QuantumCircuit. This is useful when the two qubit basis decomposer is called from within a transpiler context, as with the UnitarySynthesis pass, to avoid an extra conversion step. * Pivot to argument on __call__ and add to XXDecomposer too This commit moves the use_dag flag to the __call__ method directly instead of storing it as an instance variable. To make the interface consistent between the 2 built-in decomposers the flag is also added to the XXDecomposer class's __call__ method too. This was needed because the unitary synthesis pass calls the decomposers interchangeably and to be able to use them without type checking they both will need the flag. --- .../two_qubit/two_qubit_decompose.py | 92 ++++++++++++++----- .../two_qubit/xx_decompose/decomposer.py | 8 +- .../passes/synthesis/unitary_synthesis.py | 70 +++++++++----- ...bit-basis-decomposer-024a9ced9833289c.yaml | 18 ++++ test/python/synthesis/test_synthesis.py | 39 ++++++++ 5 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index ee6f40b58c8c..41ba75c6b237 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -29,14 +29,27 @@ import io import base64 import warnings -from typing import Optional, Type +from typing import Optional, Type, TYPE_CHECKING import logging import numpy as np from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate -from qiskit.circuit.library.standard_gates import CXGate, U3Gate, U2Gate, U1Gate +from qiskit.circuit.library.standard_gates import ( + CXGate, + U3Gate, + U2Gate, + U1Gate, + UGate, + PhaseGate, + RXGate, + RYGate, + RZGate, + SXGate, + XGate, + RGate, +) from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators import Operator from qiskit.synthesis.one_qubit.one_qubit_decompose import ( @@ -46,9 +59,28 @@ from qiskit.utils.deprecation import deprecate_func from qiskit._accelerate import two_qubit_decompose +if TYPE_CHECKING: + from qiskit.dagcircuit.dagcircuit import DAGCircuit + logger = logging.getLogger(__name__) +GATE_NAME_MAP = { + "cx": CXGate, + "rx": RXGate, + "sx": SXGate, + "x": XGate, + "rz": RZGate, + "u": UGate, + "p": PhaseGate, + "u1": U1Gate, + "u2": U2Gate, + "u3": U3Gate, + "ry": RYGate, + "r": RGate, +} + + def decompose_two_qubit_product_gate(special_unitary_matrix: np.ndarray): r"""Decompose :math:`U = U_l \otimes U_r` where :math:`U \in SU(4)`, and :math:`U_l,~U_r \in SU(2)`. @@ -481,6 +513,7 @@ class TwoQubitBasisDecomposer: If ``False``, don't attempt optimization. If ``None``, attempt optimization but don't raise if unknown. + .. automethod:: __call__ """ @@ -585,9 +618,10 @@ def __call__( unitary: Operator | np.ndarray, basis_fidelity: float | None = None, approximate: bool = True, + use_dag: bool = False, *, _num_basis_uses: int | None = None, - ) -> QuantumCircuit: + ) -> QuantumCircuit | DAGCircuit: r"""Decompose a two-qubit ``unitary`` over fixed basis and :math:`SU(2)` using the best approximation given that each basis application has a finite ``basis_fidelity``. @@ -596,6 +630,8 @@ def __call__( basis_fidelity (float or None): Fidelity to be assumed for applications of KAK Gate. If given, overrides ``basis_fidelity`` given at init. approximate (bool): Approximates if basis fidelities are less than 1.0. + use_dag (bool): If true a :class:`.DAGCircuit` is returned instead of a + :class:`QuantumCircuit` when this class is called. _num_basis_uses (int): force a particular approximation by passing a number in [0, 3]. Returns: @@ -612,26 +648,40 @@ def __call__( _num_basis_uses=_num_basis_uses, ) q = QuantumRegister(2) - circ = QuantumCircuit(q, global_phase=sequence.global_phase) - for name, params, qubits in sequence: - try: - getattr(circ, name)(*params, *qubits) - except AttributeError as exc: + if use_dag: + from qiskit.dagcircuit.dagcircuit import DAGCircuit + + dag = DAGCircuit() + dag.global_phase = sequence.global_phase + dag.add_qreg(q) + for name, params, qubits in sequence: if name == "USER_GATE": - circ.append(self.gate, qubits) - elif name == "u3": - gate = U3Gate(*params) - circ.append(gate, qubits) - elif name == "u2": - gate = U2Gate(*params) - circ.append(gate, qubits) - elif name == "u1": - gate = U1Gate(*params) - circ.append(gate, qubits) + dag.apply_operation_back(self.gate, tuple(q[x] for x in qubits), check=False) else: - raise QiskitError(f"Unknown gate {name}") from exc - - return circ + gate = GATE_NAME_MAP[name](*params) + dag.apply_operation_back(gate, tuple(q[x] for x in qubits), check=False) + return dag + else: + circ = QuantumCircuit(q, global_phase=sequence.global_phase) + for name, params, qubits in sequence: + try: + getattr(circ, name)(*params, *qubits) + except AttributeError as exc: + if name == "USER_GATE": + circ.append(self.gate, qubits) + elif name == "u3": + gate = U3Gate(*params) + circ.append(gate, qubits) + elif name == "u2": + gate = U2Gate(*params) + circ.append(gate, qubits) + elif name == "u1": + gate = U1Gate(*params) + circ.append(gate, qubits) + else: + raise QiskitError(f"Unknown gate {name}") from exc + + return circ def traces(self, target): r""" diff --git a/qiskit/synthesis/two_qubit/xx_decompose/decomposer.py b/qiskit/synthesis/two_qubit/xx_decompose/decomposer.py index 54a7b3b8da4f..e9394d3919f1 100644 --- a/qiskit/synthesis/two_qubit/xx_decompose/decomposer.py +++ b/qiskit/synthesis/two_qubit/xx_decompose/decomposer.py @@ -230,6 +230,7 @@ def __call__( unitary: Operator | np.ndarray, basis_fidelity: dict | float | None = None, approximate: bool = True, + use_dag: bool = False, ) -> QuantumCircuit: r""" Fashions a circuit which (perhaps approximately) models the special unitary operation @@ -246,6 +247,8 @@ def __call__( interpreted as ``{pi: f, pi/2: f/2, pi/3: f/3}``. If given, overrides the basis_fidelity given at init. approximate (bool): Approximates if basis fidelities are less than 1.0 . + use_dag (bool): If true a :class:`.DAGCircuit` is returned instead of a + :class:`QuantumCircuit` when this class is called. Returns: QuantumCircuit: Synthesized circuit. @@ -279,7 +282,7 @@ def __call__( and self.backup_optimizer is not None ): pi2_fidelity = 1 - strength_to_infidelity[np.pi / 2] - return self.backup_optimizer(unitary, basis_fidelity=pi2_fidelity) + return self.backup_optimizer(unitary, basis_fidelity=pi2_fidelity, use_dag=use_dag) # change to positive canonical coordinates if weyl_decomposition.c >= -EPSILON: @@ -314,5 +317,8 @@ def __call__( circ.append(UnitaryGate(weyl_decomposition.K1l), [1]) circ = self._decomposer1q(circ) + if use_dag: + from qiskit.converters import circuit_to_dag + return circuit_to_dag(circ, copy_operations=False) return circ diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 606df60869c9..5d919661a838 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -24,7 +24,6 @@ from __future__ import annotations from math import pi, inf, isclose from typing import Any -from copy import deepcopy from itertools import product from functools import partial import numpy as np @@ -147,20 +146,26 @@ def _error(circuit, target=None, qubits=None): of circuit as a weak proxy for error. """ if target is None: - return len(circuit) + if isinstance(circuit, DAGCircuit): + return len(circuit.op_nodes()) + else: + return len(circuit) gate_fidelities = [] gate_durations = [] - for inst in circuit: - inst_qubits = tuple(qubits[circuit.find_bit(q).index] for q in inst.qubits) + + def score_instruction(inst, inst_qubits): try: keys = target.operation_names_for_qargs(inst_qubits) for key in keys: target_op = target.operation_from_name(key) - if isinstance(target_op, inst.operation.base_class) and ( + if isinstance(circuit, DAGCircuit): + op = inst.op + else: + op = inst.operation + if isinstance(target_op, op.base_class) and ( target_op.is_parameterized() or all( - isclose(float(p1), float(p2)) - for p1, p2 in zip(target_op.params, inst.operation.params) + isclose(float(p1), float(p2)) for p1, p2 in zip(target_op.params, op.params) ) ): inst_props = target[key].get(inst_qubits, None) @@ -177,10 +182,22 @@ def _error(circuit, target=None, qubits=None): else: raise KeyError except KeyError as error: + if isinstance(circuit, DAGCircuit): + op = inst.op + else: + op = inst.operation raise TranspilerError( - f"Encountered a bad synthesis. " - f"Target has no {inst.operation} on qubits {qubits}." + f"Encountered a bad synthesis. " f"Target has no {op} on qubits {qubits}." ) from error + + if isinstance(circuit, DAGCircuit): + for inst in circuit.topological_op_nodes(): + inst_qubits = tuple(qubits[circuit.find_bit(q).index] for q in inst.qargs) + score_instruction(inst, inst_qubits) + else: + for inst in circuit: + inst_qubits = tuple(qubits[circuit.find_bit(q).index] for q in inst.qubits) + score_instruction(inst, inst_qubits) # TODO:return np.sum(gate_durations) return 1 - np.prod(gate_fidelities) @@ -896,24 +913,35 @@ def run(self, unitary, **options): # only decompose if needed. TODO: handle basis better synth_circuit = qs_decomposition(unitary) if (basis_gates or target) else None - - synth_dag = circuit_to_dag(synth_circuit) if synth_circuit is not None else None - return synth_dag + if synth_circuit is None: + return None + if isinstance(synth_circuit, DAGCircuit): + return synth_circuit + return circuit_to_dag(synth_circuit) def _synth_su4(self, su4_mat, decomposer2q, preferred_direction, approximation_degree): approximate = not approximation_degree == 1.0 - synth_circ = decomposer2q(su4_mat, approximate=approximate) - + synth_circ = decomposer2q(su4_mat, approximate=approximate, use_dag=True) + if not preferred_direction: + return synth_circ + synth_direction = None # if the gates in synthesis are in the opposite direction of the preferred direction # resynthesize a new operator which is the original conjugated by swaps. # this new operator is doubly mirrored from the original and is locally equivalent. - synth_direction = None - for inst in synth_circ: - if inst.operation.num_qubits == 2: - synth_direction = [synth_circ.find_bit(q).index for q in inst.qubits] - if preferred_direction and synth_direction != preferred_direction: - su4_mat_mm = deepcopy(su4_mat) + for inst in synth_circ.topological_op_nodes(): + if inst.op.num_qubits == 2: + synth_direction = [synth_circ.find_bit(q).index for q in inst.qargs] + if synth_direction is not None and synth_direction != preferred_direction: + su4_mat_mm = su4_mat.copy() su4_mat_mm[[1, 2]] = su4_mat_mm[[2, 1]] su4_mat_mm[:, [1, 2]] = su4_mat_mm[:, [2, 1]] - synth_circ = decomposer2q(su4_mat_mm, approximate=approximate).reverse_bits() + synth_circ = decomposer2q(su4_mat_mm, approximate=approximate, use_dag=True) + out_dag = DAGCircuit() + out_dag.global_phase = synth_circ.global_phase + out_dag.add_qubits(list(reversed(synth_circ.qubits))) + flip_bits = out_dag.qubits[::-1] + for node in synth_circ.topological_op_nodes(): + qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs) + out_dag.apply_operation_back(node.op, qubits, check=False) + return out_dag return synth_circ diff --git a/releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml b/releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml new file mode 100644 index 000000000000..4607560d96a0 --- /dev/null +++ b/releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml @@ -0,0 +1,18 @@ +--- +features_synthesis: + - | + Added a new argument, ``use_dag``, to the :meth:`.TwoQubitBasisDecomposer.__call__` + and :meth:`.XXDecomposer.__call__` methods. This argument is used to control whether + a :class:`.DAGCircuit` is returned when calling a :class:`.TwoQubitBasisDecomposer` + or :class:`.XXDecomposer` instance instead of the default :class:`.QuantumCircuit`. + For example:: + + from qiskit.circuit.library import CXGate + from qiskit.quantum_info import random_unitary + from qiskit.synthesis import TwoQubitBasisDecomposer + + decomposer = TwoQubitBasisDecomposer(CXGate(), euler_basis="PSX") + decomposer(random_unitary(4), use_dag=True) + + will return a :class:`.DAGCircuit` when calling the :class:`.TwoQubitBasisDecomposer` + instance ``decomposer``. diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py index d3f8560cb9b2..cb918b29146a 100644 --- a/test/python/synthesis/test_synthesis.py +++ b/test/python/synthesis/test_synthesis.py @@ -23,6 +23,7 @@ from ddt import ddt, data from qiskit import QiskitError, transpile +from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.circuit.library import ( @@ -270,6 +271,8 @@ def check_exact_decomposition( ): """Check exact decomposition for a particular target""" decomp_circuit = decomposer(target_unitary, _num_basis_uses=num_basis_uses) + if isinstance(decomp_circuit, DAGCircuit): + decomp_circuit = dag_to_circuit(decomp_circuit) if num_basis_uses is not None: self.assertEqual(num_basis_uses, decomp_circuit.count_ops().get("unitary", 0)) decomp_unitary = Operator(decomp_circuit).data @@ -1232,6 +1235,42 @@ def test_euler_basis_selection(self, euler_bases, kak_gates, seed): requested_basis = set(oneq_gates + [kak_gate_name]) self.assertTrue(decomposition_basis.issubset(requested_basis)) + @combine( + seed=range(10), + euler_bases=[ + ("U321", ["u3", "u2", "u1"]), + ("U3", ["u3"]), + ("U", ["u"]), + ("U1X", ["u1", "rx"]), + ("RR", ["r"]), + ("PSX", ["p", "sx"]), + ("ZYZ", ["rz", "ry"]), + ("ZXZ", ["rz", "rx"]), + ("XYX", ["rx", "ry"]), + ("ZSX", ["rz", "sx"]), + ("ZSXX", ["rz", "sx", "x"]), + ], + kak_gates=[ + (CXGate(), "cx"), + (CZGate(), "cz"), + (iSwapGate(), "iswap"), + (RXXGate(np.pi / 2), "rxx"), + ], + name="test_euler_basis_selection_{seed}_{euler_bases[0]}_{kak_gates[1]}", + ) + def test_use_dag(self, euler_bases, kak_gates, seed): + """Test the use_dag flag returns a correct dagcircuit with various target bases.""" + (euler_basis, oneq_gates) = euler_bases + (kak_gate, kak_gate_name) = kak_gates + with self.subTest(euler_basis=euler_basis, kak_gate=kak_gate): + decomposer = TwoQubitBasisDecomposer(kak_gate, euler_basis=euler_basis) + unitary = random_unitary(4, seed=seed) + self.assertIsInstance(decomposer(unitary, use_dag=True), DAGCircuit) + self.check_exact_decomposition(unitary.data, decomposer) + decomposition_basis = set(decomposer(unitary).count_ops()) + requested_basis = set(oneq_gates + [kak_gate_name]) + self.assertTrue(decomposition_basis.issubset(requested_basis)) + @ddt class TestPulseOptimalDecompose(CheckDecompositions): From f04eaab9c73290bd5642b6424d344165e5e4bf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:39:09 +0200 Subject: [PATCH 025/179] Update `transpile()` to convert `BackendV1` inputs to `BackendV2` with `BackendV2Converter` (#11996) * Convert to V2 inside transpile, deal with small changes in tests. * Fix lint * Add reno and apply comment from Ray's review --- qiskit/compiler/transpiler.py | 58 ++--- qiskit/providers/backend_compat.py | 18 +- qiskit/providers/fake_provider/fake_1q.py | 2 +- ...-target-in-transpile-7c04b14549a11f40.yaml | 8 + test/python/transpiler/test_1q.py | 9 +- .../transpiler/test_preset_passmanagers.py | 239 ++++++++---------- .../python/transpiler/test_pulse_gate_pass.py | 19 ++ 7 files changed, 160 insertions(+), 193 deletions(-) create mode 100644 releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 93a04b18bad7..5514bb168fa5 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -24,6 +24,7 @@ from qiskit.circuit.quantumregister import Qubit from qiskit.dagcircuit import DAGCircuit from qiskit.providers.backend import Backend +from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.models import BackendProperties from qiskit.pulse import Schedule, InstructionScheduleMap from qiskit.transpiler import Layout, CouplingMap, PropertySet @@ -316,6 +317,12 @@ def callback_func(**kwargs): config = user_config.get_config() optimization_level = config.get("transpile_optimization_level", 1) + if backend is not None and getattr(backend, "version", 0) <= 1: + # This is a temporary conversion step to allow for a smoother transition + # to a fully target-based transpiler pipeline while maintaining the behavior + # of `transpile` with BackendV1 inputs. + backend = BackendV2Converter(backend) + if ( scheduling_method is not None and backend is None @@ -471,14 +478,8 @@ def _check_circuits_coupling_map(circuits, cmap, backend): if cmap is not None: max_qubits = cmap.size() elif backend is not None: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if not backend.configuration().simulator: - max_qubits = backend.configuration().n_qubits - else: - max_qubits = None - else: - max_qubits = backend.num_qubits + max_qubits = backend.num_qubits + for circuit in circuits: # If coupling_map is not None or num_qubits == 1 num_qubits = len(circuit.qubits) @@ -496,27 +497,15 @@ def _log_transpile_time(start_time, end_time): def _parse_inst_map(inst_map, backend): # try getting inst_map from user, else backend - if inst_map is None: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if hasattr(backend, "defaults"): - inst_map = getattr(backend.defaults(), "instruction_schedule_map", None) - else: - inst_map = backend.target.instruction_schedule_map() + if inst_map is None and backend is not None: + inst_map = backend.target.instruction_schedule_map() return inst_map def _parse_coupling_map(coupling_map, backend): # try getting coupling_map from user, else backend - if coupling_map is None: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if getattr(backend, "configuration", None): - configuration = backend.configuration() - if hasattr(configuration, "coupling_map") and configuration.coupling_map: - coupling_map = CouplingMap(configuration.coupling_map) - else: - coupling_map = backend.coupling_map + if coupling_map is None and backend is not None: + coupling_map = backend.coupling_map # coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]] if coupling_map is None or isinstance(coupling_map, CouplingMap): @@ -553,14 +542,8 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuit): take precedence over backend durations, but be superceded by ``inst_duration``s. """ if not inst_durations: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - backend_durations = InstructionDurations() - try: - backend_durations = InstructionDurations.from_backend(backend) - except AttributeError: - pass - else: + backend_durations = InstructionDurations() + if backend is not None: backend_durations = backend.instruction_durations circ_durations = InstructionDurations() @@ -629,13 +612,6 @@ def _parse_timing_constraints(backend, timing_constraints): return timing_constraints if backend is None and timing_constraints is None: timing_constraints = TimingConstraints() - else: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if timing_constraints is None: - # get constraints from backend - timing_constraints = getattr(backend.configuration(), "timing_constraints", {}) - timing_constraints = TimingConstraints(**timing_constraints) - else: - timing_constraints = backend.target.timing_constraints() + elif backend is not None: + timing_constraints = backend.target.timing_constraints() return timing_constraints diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index de57f3f09fa2..e567c330a958 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.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 @@ -57,7 +57,7 @@ def convert_to_target( A ``Target`` instance. """ - # importing pacakges where they are needed, to avoid cyclic-import. + # importing packages where they are needed, to avoid cyclic-import. # pylint: disable=cyclic-import from qiskit.transpiler.target import ( Target, @@ -82,7 +82,7 @@ def convert_to_target( "switch_case": SwitchCaseOp, } - in_data = {"num_qubits": configuration.n_qubits} + in_data = {"num_qubits": configuration.num_qubits} # Parse global configuration properties if hasattr(configuration, "dt"): @@ -97,7 +97,6 @@ def convert_to_target( all_instructions = set.union( basis_gates, set(required), supported_instructions.intersection(CONTROL_FLOW_OP_NAMES) ) - inst_name_map = {} # type: Dict[str, Instruction] faulty_ops = set() @@ -244,10 +243,8 @@ def _get_value(prop_dict, prop_name): for name in inst_sched_map.instructions: for qubits in inst_sched_map.qubits_with_instruction(name): - if not isinstance(qubits, tuple): qubits = (qubits,) - if ( name not in all_instructions or name not in prop_name_map @@ -267,17 +264,18 @@ def _get_value(prop_dict, prop_name): continue entry = inst_sched_map._get_calibration_entry(name, qubits) - try: prop_name_map[name][qubits].calibration = entry except AttributeError: + # if instruction properties are "None", add entry + prop_name_map[name].update({qubits: InstructionProperties(None, None, entry)}) logger.info( "The PulseDefaults payload received contains an instruction %s on " - "qubits %s which is not present in the configuration or properties payload.", + "qubits %s which is not present in the configuration or properties payload." + "A new properties entry will be added to include the new calibration data.", name, qubits, ) - # Add parsed properties to target target = Target(**in_data) for inst_name in all_instructions: @@ -384,7 +382,7 @@ def __init__( super().__init__( provider=backend.provider, name=backend.name(), - description=self._config.description, + description=getattr(self._config, "description", None), online_date=getattr(self._config, "online_date", None), backend_version=self._config.backend_version, ) diff --git a/qiskit/providers/fake_provider/fake_1q.py b/qiskit/providers/fake_provider/fake_1q.py index 07589476149e..09959620bc92 100644 --- a/qiskit/providers/fake_provider/fake_1q.py +++ b/qiskit/providers/fake_provider/fake_1q.py @@ -32,7 +32,7 @@ def __init__(self): configuration = BackendProperties( backend_name="fake_1q", backend_version="0.0.0", - n_qubits=1, + num_qubits=1, basis_gates=["u1", "u2", "u3", "cx"], simulator=False, local=True, diff --git a/releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml b/releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml new file mode 100644 index 000000000000..8e385ba01949 --- /dev/null +++ b/releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The :func:`.transpile` function has been upgraded to internally convert + `backend` inputs of type :class:`.BackendV1` to :class:`.BackendV2`, + which allows the transpilation pipeline to now access the backend + constraints through a :class:`.Target`. This change does not require any + user action. \ No newline at end of file diff --git a/test/python/transpiler/test_1q.py b/test/python/transpiler/test_1q.py index 31975456f346..50bdc7b24643 100644 --- a/test/python/transpiler/test_1q.py +++ b/test/python/transpiler/test_1q.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (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 @@ -17,6 +17,7 @@ from qiskit import QuantumCircuit from qiskit.compiler import transpile from qiskit.providers.fake_provider import Fake1Q +from qiskit.providers.basic_provider import BasicSimulator from qiskit.transpiler import TranspilerError from test import combine # pylint: disable=wrong-import-order from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -76,9 +77,7 @@ def test_device(self, circuit, level): name="{circuit.__name__}_level{level}_valid", ) def test_simulator(self, circuit, level): - """All the levels with all the 1Q simulator backend""" - # Set fake backend config to simulator - backend = Fake1Q() - backend._configuration.simulator = True + """All the levels with a simulator backend""" + backend = BasicSimulator() result = transpile(circuit(), backend=backend, optimization_level=level, seed_transpiler=42) self.assertIsInstance(result, QuantumCircuit) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index ae1837bf111b..247aa82ec039 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -324,7 +324,7 @@ def test_backend(self, level): qc = QuantumCircuit(qr) qc.cx(qr[2], qr[4]) - backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP) + backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP, seed=42) _ = transpile(qc, backend, optimization_level=level, callback=self.callback) @@ -413,7 +413,7 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" - target = TargetBackend(num_qubits=7) + target = TargetBackend(num_qubits=7, seed=42) qr = QuantumRegister(2, "q") qc = QuantumCircuit(qr) qc.h(qr[0]) @@ -425,7 +425,7 @@ def get_translation_stage_plugin(self): def test_level1_runs_vf2post_layout_when_routing_required(self): """Test that if we run routing as part of sabre layout VF2PostLayout runs.""" - target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP) + target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) qc = QuantumCircuit(5) qc.h(0) qc.cy(0, 1) @@ -448,7 +448,7 @@ def test_level1_runs_vf2post_layout_when_routing_required(self): def test_level1_runs_vf2post_layout_when_routing_method_set_and_required(self): """Test that if we run routing as part of sabre layout VF2PostLayout runs.""" - target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP) + target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) qc = QuantumCircuit(5) qc.h(0) qc.cy(0, 1) @@ -473,7 +473,10 @@ def test_level1_runs_vf2post_layout_when_routing_method_set_and_required(self): def test_level1_not_runs_vf2post_layout_when_layout_method_set(self): """Test that if we don't run VF2PostLayout with custom layout_method.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) qc = QuantumCircuit(5) qc.h(0) @@ -495,7 +498,10 @@ def test_level1_not_runs_vf2post_layout_when_layout_method_set(self): def test_level1_not_run_vf2post_layout_when_trivial_is_perfect(self): """Test that if we find a trivial perfect layout we don't run vf2post.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) qc = QuantumCircuit(2) qc.h(0) @@ -512,7 +518,10 @@ def test_level1_not_run_vf2post_layout_when_trivial_is_perfect(self): def test_level1_not_run_vf2post_layout_when_vf2layout_is_perfect(self): """Test that if we find a vf2 perfect layout we don't run vf2post.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) qc = QuantumCircuit(4) qc.h(0) @@ -531,7 +540,10 @@ def test_level1_not_run_vf2post_layout_when_vf2layout_is_perfect(self): def test_level1_runs_vf2post_layout_when_routing_required_control_flow(self): """Test that if we run routing as part of sabre layout VF2PostLayout runs.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) _target = target.target target._target.add_instruction(ForLoopOp, name="for_loop") @@ -558,7 +570,10 @@ def test_level1_runs_vf2post_layout_when_routing_required_control_flow(self): def test_level1_not_runs_vf2post_layout_when_layout_method_set_control_flow(self): """Test that if we don't run VF2PostLayout with custom layout_method.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) _target = target.target target._target.add_instruction(ForLoopOp, name="for_loop") @@ -584,7 +599,10 @@ def test_level1_not_runs_vf2post_layout_when_layout_method_set_control_flow(self def test_level1_not_run_vf2post_layout_when_trivial_is_perfect_control_flow(self): """Test that if we find a trivial perfect layout we don't run vf2post.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) _target = target.target target._target.add_instruction(ForLoopOp, name="for_loop") @@ -604,7 +622,10 @@ def test_level1_not_run_vf2post_layout_when_trivial_is_perfect_control_flow(self def test_level1_not_run_vf2post_layout_when_vf2layout_is_perfect_control_flow(self): """Test that if we find a vf2 perfect layout we don't run vf2post.""" target = GenericBackendV2( - num_qubits=7, basis_gates=["cx", "id", "rz", "sx", "x"], coupling_map=LAGOS_CMAP + num_qubits=7, + basis_gates=["cx", "id", "rz", "sx", "x"], + coupling_map=LAGOS_CMAP, + seed=42, ) _target = target.target target._target.add_instruction(ForLoopOp, name="for_loop") @@ -630,7 +651,7 @@ class TestInitialLayouts(QiskitTestCase): @data(0, 1, 2, 3) def test_layout_1711(self, level): - """Test that a user-given initial layout is respected, + """Test that a user-given initial layout is respected in the qobj. See: https://github.com/Qiskit/qiskit-terra/issues/1711 @@ -661,9 +682,7 @@ def test_layout_1711(self, level): 14: ancilla[12], 15: qr[2], } - - backend = Fake20QV1() - backend.configuration().coupling_map = RUESCHLIKON_CMAP + backend = GenericBackendV2(num_qubits=16, coupling_map=RUESCHLIKON_CMAP, seed=42) qc_b = transpile(qc, backend, initial_layout=initial_layout, optimization_level=level) qobj = assemble(qc_b) @@ -672,7 +691,7 @@ def test_layout_1711(self, level): compiled_ops = qobj.experiments[0].instructions for operation in compiled_ops: if operation.name == "cx": - self.assertIn(operation.qubits, backend.configuration().coupling_map) + self.assertIn(tuple(operation.qubits), backend.coupling_map) self.assertIn(operation.qubits, [[15, 0], [15, 2]]) @data(0, 1, 2, 3) @@ -711,10 +730,8 @@ def test_layout_2532(self, level): 12: ancilla[7], 13: ancilla[8], } - backend = Fake20QV1() - backend.configuration().coupling_map = MELBOURNE_CMAP + backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP, seed=42) qc_b = transpile(qc, backend, initial_layout=initial_layout, optimization_level=level) - self.assertEqual(qc_b._layout.initial_layout._p2v, final_layout) output_qr = qc_b.qregs[0] @@ -766,7 +783,6 @@ def test_layout_2503(self, level): } backend = Fake20QV1() - qc_b = transpile(qc, backend, initial_layout=initial_layout, optimization_level=level) self.assertEqual(qc_b._layout.initial_layout._p2v, final_layout) @@ -796,50 +812,51 @@ def test_layout_tokyo_2845(self, level): qc.cx(qr1[2], qr2[0]) qc.cx(qr2[0], qr2[1]) + ancilla = QuantumRegister(15, "ancilla") trivial_layout = { - 0: Qubit(QuantumRegister(3, "qr1"), 0), - 1: Qubit(QuantumRegister(3, "qr1"), 1), - 2: Qubit(QuantumRegister(3, "qr1"), 2), - 3: Qubit(QuantumRegister(2, "qr2"), 0), - 4: Qubit(QuantumRegister(2, "qr2"), 1), - 5: Qubit(QuantumRegister(15, "ancilla"), 0), - 6: Qubit(QuantumRegister(15, "ancilla"), 1), - 7: Qubit(QuantumRegister(15, "ancilla"), 2), - 8: Qubit(QuantumRegister(15, "ancilla"), 3), - 9: Qubit(QuantumRegister(15, "ancilla"), 4), - 10: Qubit(QuantumRegister(15, "ancilla"), 5), - 11: Qubit(QuantumRegister(15, "ancilla"), 6), - 12: Qubit(QuantumRegister(15, "ancilla"), 7), - 13: Qubit(QuantumRegister(15, "ancilla"), 8), - 14: Qubit(QuantumRegister(15, "ancilla"), 9), - 15: Qubit(QuantumRegister(15, "ancilla"), 10), - 16: Qubit(QuantumRegister(15, "ancilla"), 11), - 17: Qubit(QuantumRegister(15, "ancilla"), 12), - 18: Qubit(QuantumRegister(15, "ancilla"), 13), - 19: Qubit(QuantumRegister(15, "ancilla"), 14), + 0: qr1[0], + 1: qr1[1], + 2: qr1[2], + 3: qr2[0], + 4: qr2[1], + 5: ancilla[0], + 6: ancilla[1], + 7: ancilla[2], + 8: ancilla[3], + 9: ancilla[4], + 10: ancilla[5], + 11: ancilla[6], + 12: ancilla[7], + 13: ancilla[8], + 14: ancilla[9], + 15: ancilla[10], + 16: ancilla[11], + 17: ancilla[12], + 18: ancilla[13], + 19: ancilla[14], } vf2_layout = { - 0: Qubit(QuantumRegister(15, "ancilla"), 0), - 1: Qubit(QuantumRegister(15, "ancilla"), 1), - 2: Qubit(QuantumRegister(15, "ancilla"), 2), - 3: Qubit(QuantumRegister(15, "ancilla"), 3), - 4: Qubit(QuantumRegister(15, "ancilla"), 4), - 5: Qubit(QuantumRegister(15, "ancilla"), 5), - 6: Qubit(QuantumRegister(15, "ancilla"), 6), - 7: Qubit(QuantumRegister(15, "ancilla"), 7), - 8: Qubit(QuantumRegister(3, "qr1"), 1), - 9: Qubit(QuantumRegister(15, "ancilla"), 8), - 10: Qubit(QuantumRegister(15, "ancilla"), 9), - 11: Qubit(QuantumRegister(15, "ancilla"), 10), - 12: Qubit(QuantumRegister(3, "qr1"), 0), - 13: Qubit(QuantumRegister(3, "qr1"), 2), - 14: Qubit(QuantumRegister(2, "qr2"), 1), - 15: Qubit(QuantumRegister(15, "ancilla"), 11), - 16: Qubit(QuantumRegister(15, "ancilla"), 12), - 17: Qubit(QuantumRegister(15, "ancilla"), 13), - 18: Qubit(QuantumRegister(15, "ancilla"), 14), - 19: Qubit(QuantumRegister(2, "qr2"), 0), + 0: ancilla[0], + 1: ancilla[1], + 2: ancilla[2], + 3: ancilla[3], + 4: ancilla[4], + 5: qr1[2], + 6: qr2[0], + 7: qr2[1], + 8: ancilla[5], + 9: ancilla[6], + 10: qr1[1], + 11: qr1[0], + 12: ancilla[7], + 13: ancilla[8], + 14: ancilla[9], + 15: ancilla[10], + 16: ancilla[11], + 17: ancilla[12], + 18: ancilla[13], + 19: ancilla[14], } # Trivial layout @@ -856,8 +873,8 @@ def test_layout_tokyo_2845(self, level): expected_layout_level2, expected_layout_level3, ] - backend = Fake20QV1() - backend.configuration().coupling_map = TOKYO_CMAP + + backend = GenericBackendV2(num_qubits=20, coupling_map=TOKYO_CMAP, seed=42) result = transpile(qc, backend, optimization_level=level, seed_transpiler=42) self.assertEqual(result._layout.initial_layout._p2v, expected_layouts[level]) @@ -904,64 +921,18 @@ def test_layout_tokyo_fully_connected_cx(self, level): 2: ancilla[2], 3: ancilla[3], 4: ancilla[4], - 5: qr[2], - 6: qr[1], - 7: ancilla[6], - 8: ancilla[7], - 9: ancilla[8], - 10: qr[3], - 11: qr[0], - 12: ancilla[9], - 13: ancilla[10], - 14: ancilla[11], - 15: ancilla[5], - 16: qr[4], - 17: ancilla[12], - 18: ancilla[13], - 19: ancilla[14], - } - - sabre_layout_lvl_2 = { - 0: ancilla[0], - 1: ancilla[1], - 2: ancilla[2], - 3: ancilla[3], - 4: ancilla[4], - 5: qr[2], - 6: qr[1], - 7: ancilla[6], - 8: ancilla[7], - 9: ancilla[8], - 10: qr[3], - 11: qr[0], - 12: ancilla[9], - 13: ancilla[10], - 14: ancilla[11], - 15: ancilla[5], - 16: qr[4], - 17: ancilla[12], - 18: ancilla[13], - 19: ancilla[14], - } - - sabre_layout_lvl_3 = { - 0: ancilla[0], - 1: ancilla[1], - 2: ancilla[2], - 3: ancilla[3], - 4: ancilla[4], - 5: qr[2], - 6: qr[1], - 7: ancilla[6], - 8: ancilla[7], - 9: ancilla[8], - 10: qr[3], - 11: qr[0], - 12: ancilla[9], - 13: ancilla[10], - 14: ancilla[11], - 15: ancilla[5], - 16: qr[4], + 5: qr[1], + 6: qr[0], + 7: qr[4], + 8: ancilla[6], + 9: ancilla[7], + 10: qr[2], + 11: qr[3], + 12: ancilla[5], + 13: ancilla[8], + 14: ancilla[9], + 15: ancilla[10], + 16: ancilla[11], 17: ancilla[12], 18: ancilla[13], 19: ancilla[14], @@ -969,8 +940,8 @@ def test_layout_tokyo_fully_connected_cx(self, level): expected_layout_level0 = trivial_layout expected_layout_level1 = sabre_layout - expected_layout_level2 = sabre_layout_lvl_2 - expected_layout_level3 = sabre_layout_lvl_3 + expected_layout_level2 = sabre_layout + expected_layout_level3 = sabre_layout expected_layouts = [ expected_layout_level0, @@ -978,9 +949,7 @@ def test_layout_tokyo_fully_connected_cx(self, level): expected_layout_level2, expected_layout_level3, ] - backend = Fake20QV1() - backend.configuration().coupling_map = TOKYO_CMAP - + backend = GenericBackendV2(num_qubits=20, coupling_map=TOKYO_CMAP, seed=42) result = transpile(qc, backend, optimization_level=level, seed_transpiler=42) self.assertEqual(result._layout.initial_layout._p2v, expected_layouts[level]) @@ -991,12 +960,10 @@ def test_all_levels_use_trivial_if_perfect(self, level): See: https://github.com/Qiskit/qiskit-terra/issues/5694 for more details """ - backend = Fake20QV1() - backend.configuration().coupling_map = TOKYO_CMAP - config = backend.configuration() + backend = GenericBackendV2(num_qubits=20, coupling_map=TOKYO_CMAP, seed=42) - rows = [x[0] for x in config.coupling_map] - cols = [x[1] for x in config.coupling_map] + rows = [x[0] for x in backend.coupling_map] + cols = [x[1] for x in backend.coupling_map] adjacency_matrix = np.zeros((20, 20)) adjacency_matrix[rows, cols] = 1 @@ -1255,7 +1222,7 @@ def test_with_backend(self, optimization_level): @data(0, 1, 2, 3) def test_with_no_backend(self, optimization_level): """Test a passmanager is constructed with no backend and optimization level.""" - target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP) + target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager( optimization_level, coupling_map=target.coupling_map, @@ -1270,7 +1237,7 @@ def test_with_no_backend(self, optimization_level): @data(0, 1, 2, 3) def test_with_no_backend_only_target(self, optimization_level): """Test a passmanager is constructed with a manual target and optimization level.""" - target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP) + target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, target=target.target) self.assertIsInstance(pm, PassManager) @@ -1299,7 +1266,7 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" - target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP) + target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, backend=target) self.assertIsInstance(pm, PassManager) @@ -1331,7 +1298,7 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" - target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP) + target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, backend=target) self.assertIsInstance(pm, PassManager) @@ -1363,7 +1330,7 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" - target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP) + target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, backend=target) self.assertIsInstance(pm, PassManager) @@ -1395,7 +1362,7 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" - target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP) + target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, backend=target) self.assertIsInstance(pm, PassManager) diff --git a/test/python/transpiler/test_pulse_gate_pass.py b/test/python/transpiler/test_pulse_gate_pass.py index 8de8ceb66ef8..a11d4c4a6b53 100644 --- a/test/python/transpiler/test_pulse_gate_pass.py +++ b/test/python/transpiler/test_pulse_gate_pass.py @@ -16,6 +16,7 @@ from qiskit import pulse, circuit, transpile from qiskit.providers.fake_provider import Fake27QPulseV1, GenericBackendV2 +from qiskit.providers.models import GateConfig from qiskit.quantum_info.random import random_unitary from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -185,6 +186,12 @@ def test_transpile_with_custom_gate(self): backend.defaults().instruction_schedule_map.add( "my_gate", (1,), self.my_gate_q1, arguments=["P0"] ) + # Add gate to backend configuration + backend.configuration().basis_gates.append("my_gate") + dummy_config = GateConfig( + name="my_gate", parameters=[], qasm_def="", coupling_map=[(0,), (1,)] + ) + backend.configuration().gates.append(dummy_config) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} @@ -212,6 +219,10 @@ def test_transpile_with_parameterized_custom_gate(self): backend.defaults().instruction_schedule_map.add( "my_gate", (0,), self.my_gate_q0, arguments=["P0"] ) + # Add gate to backend configuration + backend.configuration().basis_gates.append("my_gate") + dummy_config = GateConfig(name="my_gate", parameters=[], qasm_def="", coupling_map=[(0,)]) + backend.configuration().gates.append(dummy_config) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} @@ -237,6 +248,10 @@ def test_transpile_with_multiple_circuits(self): backend.defaults().instruction_schedule_map.add( "my_gate", (0,), self.my_gate_q0, arguments=["P0"] ) + # Add gate to backend configuration + backend.configuration().basis_gates.append("my_gate") + dummy_config = GateConfig(name="my_gate", parameters=[], qasm_def="", coupling_map=[(0,)]) + backend.configuration().gates.append(dummy_config) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} @@ -263,6 +278,10 @@ def test_multiple_instructions_with_different_parameters(self): backend.defaults().instruction_schedule_map.add( "my_gate", (0,), self.my_gate_q0, arguments=["P0"] ) + # Add gate to backend configuration + backend.configuration().basis_gates.append("my_gate") + dummy_config = GateConfig(name="my_gate", parameters=[], qasm_def="", coupling_map=[(0,)]) + backend.configuration().gates.append(dummy_config) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} From 686ff139a651e785187b8cd037efd3b56df1db2b Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 25 Apr 2024 20:27:23 +0100 Subject: [PATCH 026/179] Finalise support for Numpy 2.0 (#11999) * Finalise support for Numpy 2.0 This commit brings the Qiskit test suite to a passing state (with all optionals installed) with Numpy 2.0.0b1, building on previous commits that handled much of the rest of the changing requirements: - gh-10890 - gh-10891 - gh-10892 - gh-10897 - gh-11023 Notably, this commit did not actually require a rebuild of Qiskit, despite us compiling against Numpy; it seems to happen that the C API stuff we use via `rust-numpy` (which loads the Numpy C extensions dynamically during module initialisation) hasn't changed. The main changes are: - adapting to the changed `copy=None` and `copy=False` semantics in `array` and `asarray`. - making sure all our implementers of `__array__` accept both `dtype` and `copy` arguments. Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com> * Update `__array__` methods for Numpy 2.0 compatibility As of Numpy 2.0, implementers of `__array__` are expected and required to have a signature def __array__(self, dtype=None, copy=None): ... In Numpys before 2.0, the `copy` argument will never be passed, and the expected signature was def __array__(self, dtype=None): ... Because of this, we have latitude to set `copy` in our implementations to anything we like if we're running against Numpy 1.x, but we should default to `copy=None` if we're running against Numpy 2.0. The semantics of the `copy` argument to `np.array` changed in Numpy 2.0. Now, `copy=False` means "raise a `ValueError` if a copy is required" and `copy=None` means "copy only if required". In Numpy 1.x, `copy=False` meant "copy only if required". In _both_ Numpy 1.x and 2.0, `ndarray.astype` takes a `copy` argument, and in both, `copy=False` means "copy only if required". In Numpy 2.0 only, `np.asarray` gained a `copy` argument with the same semantics as the `np.array` copy argument from Numpy 2.0. Further, the semantics of the `__array__` method changed in Numpy 2.0, particularly around copying. Now, Numpy will assume that it can pass `copy=True` and the implementer will handle this. If `copy=False` is given and a copy or calculation is required, then the implementer is required to raise `ValueError`. We have a few places where the `__array__` method may (or always does) calculate the array, so in all these, we must forbid `copy=False`. With all this in mind: this PR sets up all our implementers of `__array__` to either default to `copy=None` if they will never actually need to _use_ the `copy` argument within themselves (except perhaps to test if it was set by Numpy 2.0 to `False`, as Numpy 1.x will never set it), or to a compatibility shim `_numpy_compat.COPY_ONLY_IF_NEEDED` if they do naturally want to use it with those semantics. The pattern def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED): dtype = self._array.dtype if dtype is None else dtype return np.array(self._array, dtype=dtype, copy=copy) using `array` instead of `asarray` lets us achieve all the desired behaviour between the interactions of `dtype` and `copy` in a way that is compatible with both Numpy 1.x and 2.x. * fixing numerical issues on mac-arm * Change error to match Numpy --------- Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com> Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com> --- qiskit/__init__.py | 1 + qiskit/_numpy_compat.py | 73 +++++++++++++++++++ qiskit/circuit/__init__.py | 7 +- qiskit/circuit/_utils.py | 23 ++++-- qiskit/circuit/delay.py | 6 +- .../library/generalized_gates/pauli.py | 4 +- .../library/generalized_gates/permutation.py | 5 +- .../library/generalized_gates/unitary.py | 7 +- qiskit/circuit/library/hamiltonian_gate.py | 11 ++- .../library/standard_gates/global_phase.py | 6 +- qiskit/circuit/library/standard_gates/p.py | 8 +- qiskit/circuit/library/standard_gates/r.py | 4 +- qiskit/circuit/library/standard_gates/rx.py | 8 +- qiskit/circuit/library/standard_gates/rxx.py | 4 +- qiskit/circuit/library/standard_gates/ry.py | 8 +- qiskit/circuit/library/standard_gates/ryy.py | 4 +- qiskit/circuit/library/standard_gates/rz.py | 8 +- qiskit/circuit/library/standard_gates/rzx.py | 4 +- qiskit/circuit/library/standard_gates/rzz.py | 4 +- qiskit/circuit/library/standard_gates/u.py | 14 ++-- qiskit/circuit/library/standard_gates/u1.py | 8 +- qiskit/circuit/library/standard_gates/u2.py | 6 +- qiskit/circuit/library/standard_gates/u3.py | 14 ++-- .../library/standard_gates/xx_minus_yy.py | 4 +- .../library/standard_gates/xx_plus_yy.py | 9 ++- qiskit/primitives/backend_sampler_v2.py | 2 +- .../containers/observables_array.py | 4 +- qiskit/primitives/containers/shape.py | 2 +- qiskit/quantum_info/operators/channel/chi.py | 12 +-- qiskit/quantum_info/operators/channel/choi.py | 16 ++-- qiskit/quantum_info/operators/channel/ptm.py | 12 +-- .../quantum_info/operators/channel/superop.py | 18 ++--- .../operators/dihedral/dihedral.py | 9 ++- qiskit/quantum_info/operators/operator.py | 24 +++--- qiskit/quantum_info/operators/scalar_op.py | 19 ++--- .../operators/symplectic/clifford.py | 22 ++++-- .../operators/symplectic/pauli.py | 9 ++- .../operators/symplectic/pauli_list.py | 7 +- .../operators/symplectic/sparse_pauli_op.py | 16 ++-- qiskit/quantum_info/states/densitymatrix.py | 26 +++---- qiskit/quantum_info/states/statevector.py | 24 +++--- qiskit/visualization/array.py | 2 +- .../notes/numpy-2.0-2f3e35bd42c48518.yaml | 5 ++ requirements.txt | 4 +- test/python/providers/test_backend_v2.py | 17 +++-- .../transpiler/test_dynamical_decoupling.py | 4 +- 46 files changed, 338 insertions(+), 166 deletions(-) create mode 100644 qiskit/_numpy_compat.py create mode 100644 releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 1ee0da3dc737..590a5698a77d 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -53,6 +53,7 @@ import qiskit._accelerate +import qiskit._numpy_compat # Globally define compiled submodules. The normal import mechanism will not find compiled submodules # in _accelerate because it relies on file paths, but PyO3 generates only one shared library file. diff --git a/qiskit/_numpy_compat.py b/qiskit/_numpy_compat.py new file mode 100644 index 000000000000..a6c06671c986 --- /dev/null +++ b/qiskit/_numpy_compat.py @@ -0,0 +1,73 @@ +# 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. + +"""Compatiblity helpers for the Numpy 1.x to 2.0 transition.""" + +import re +import typing +import warnings + +import numpy as np + +# This version pattern is taken from the pypa packaging project: +# https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254 which is dual licensed +# Apache 2.0 and BSD see the source for the original authors and other details. +_VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+VERSION = np.lib.NumpyVersion(np.__version__)
+VERSION_PARTS: typing.Tuple[int, ...]
+"""The numeric parts of the Numpy release version, e.g. ``(2, 0, 0)``.  Does not include pre- or
+post-release markers (e.g. ``rc1``)."""
+if match := re.fullmatch(_VERSION_PATTERN, np.__version__, flags=re.VERBOSE | re.IGNORECASE):
+    # Assuming Numpy won't ever introduce epochs, and we don't care about pre/post markers.
+    VERSION_PARTS = tuple(int(x) for x in match["release"].split("."))
+else:
+    # Just guess a version.  We know all existing Numpys have good version strings, so the only way
+    # this should trigger is from a new or a dev version.
+    warnings.warn(
+        f"Unrecognized version string for Numpy: '{np.__version__}'.  Assuming Numpy 2.0.",
+        RuntimeWarning,
+    )
+    VERSION_PARTS = (2, 0, 0)
+
+COPY_ONLY_IF_NEEDED = None if VERSION_PARTS >= (2, 0, 0) else False
+"""The sentinel value given to ``np.array`` and ``np.ndarray.astype`` (etc) to indicate that a copy
+should be made only if required."""
diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py
index 2e8603af9f7d..9fbefb4c5d9f 100644
--- a/qiskit/circuit/__init__.py
+++ b/qiskit/circuit/__init__.py
@@ -833,7 +833,7 @@
 ``__array__``.  This is used by :meth:`Gate.to_matrix`, and has the signature:
 
 .. currentmodule:: None
-.. py:method:: __array__(dtype=None)
+.. py:method:: __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
@@ -875,7 +875,9 @@ def power(self, exponent: float):
             # Also we have an efficient representation of power.
             return RXZGate(exponent * self.params[0])
 
-        def __array__(self, dtype=None):
+        def __array__(self, dtype=None, copy=None):
+            if copy is False:
+                raise ValueError("unable to avoid copy while creating an array as requested")
             cos = math.cos(0.5 * self.params[0])
             isin = 1j * math.sin(0.5 * self.params[0])
             return np.array([
@@ -1340,6 +1342,7 @@ def __array__(self, dtype=None):
 """
 
 from .exceptions import CircuitError
+from . import _utils
 from .quantumcircuit import QuantumCircuit
 from .classicalregister import ClassicalRegister, Clbit
 from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
diff --git a/qiskit/circuit/_utils.py b/qiskit/circuit/_utils.py
index cfde85bad8dd..86a058e88525 100644
--- a/qiskit/circuit/_utils.py
+++ b/qiskit/circuit/_utils.py
@@ -15,6 +15,8 @@
 
 import math
 import numpy
+
+from qiskit import _numpy_compat
 from qiskit.exceptions import QiskitError
 from qiskit.circuit.exceptions import CircuitError
 from .parametervector import ParameterVectorElement
@@ -117,8 +119,9 @@ def with_gate_array(base_array):
     nonwritable = numpy.array(base_array, dtype=numpy.complex128)
     nonwritable.setflags(write=False)
 
-    def __array__(_self, dtype=None):
-        return numpy.asarray(nonwritable, dtype=dtype)
+    def __array__(_self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = nonwritable.dtype if dtype is None else dtype
+        return numpy.array(nonwritable, dtype=dtype, copy=copy)
 
     def decorator(cls):
         if hasattr(cls, "__array__"):
@@ -149,15 +152,21 @@ def matrix_for_control_state(state):
     if cached_states is None:
         nonwritables = [matrix_for_control_state(state) for state in range(2**num_ctrl_qubits)]
 
-        def __array__(self, dtype=None):
-            return numpy.asarray(nonwritables[self.ctrl_state], dtype=dtype)
+        def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+            arr = nonwritables[self.ctrl_state]
+            dtype = arr.dtype if dtype is None else dtype
+            return numpy.array(arr, dtype=dtype, copy=copy)
 
     else:
         nonwritables = {state: matrix_for_control_state(state) for state in cached_states}
 
-        def __array__(self, dtype=None):
-            if (out := nonwritables.get(self.ctrl_state)) is not None:
-                return numpy.asarray(out, dtype=dtype)
+        def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+            if (arr := nonwritables.get(self.ctrl_state)) is not None:
+                dtype = arr.dtype if dtype is None else dtype
+                return numpy.array(arr, dtype=dtype, copy=copy)
+
+            if copy is False and copy is not _numpy_compat.COPY_ONLY_IF_NEEDED:
+                raise ValueError("could not produce matrix without calculation")
             return numpy.asarray(
                 _compute_control_matrix(base, num_ctrl_qubits, self.ctrl_state), dtype=dtype
             )
diff --git a/qiskit/circuit/delay.py b/qiskit/circuit/delay.py
index 16d84d15cbe5..a333125a5a2b 100644
--- a/qiskit/circuit/delay.py
+++ b/qiskit/circuit/delay.py
@@ -17,9 +17,11 @@
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.gate import Gate
+from qiskit.circuit import _utils
 from qiskit.circuit.parameterexpression import ParameterExpression
 
 
+@_utils.with_gate_array(np.eye(2, dtype=complex))
 class Delay(Instruction):
     """Do nothing and just delay/wait/idle for a specified duration."""
 
@@ -53,10 +55,6 @@ def duration(self, duration):
         """Set the duration of this delay."""
         self.params = [duration]
 
-    def __array__(self, dtype=None):
-        """Return the identity matrix."""
-        return np.array([[1, 0], [0, 1]], dtype=dtype)
-
     def to_matrix(self) -> np.ndarray:
         """Return a Numpy.array for the unitary matrix. This has been
         added to enable simulation without making delay a full Gate type.
diff --git a/qiskit/circuit/library/generalized_gates/pauli.py b/qiskit/circuit/library/generalized_gates/pauli.py
index e8b063a75e9e..01bbd09c1979 100644
--- a/qiskit/circuit/library/generalized_gates/pauli.py
+++ b/qiskit/circuit/library/generalized_gates/pauli.py
@@ -63,13 +63,13 @@ def inverse(self, annotated: bool = False):
         r"""Return inverted pauli gate (itself)."""
         return PauliGate(self.params[0])  # self-inverse
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the pauli gate.
         i.e. tensor product of the paulis"""
         # pylint: disable=cyclic-import
         from qiskit.quantum_info.operators import Pauli
 
-        return Pauli(self.params[0]).__array__(dtype=dtype)
+        return Pauli(self.params[0]).__array__(dtype=dtype, copy=copy)
 
     def validate_parameter(self, parameter):
         if isinstance(parameter, str):
diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py
index 8888344c78bc..776c69d94f01 100644
--- a/qiskit/circuit/library/generalized_gates/permutation.py
+++ b/qiskit/circuit/library/generalized_gates/permutation.py
@@ -147,8 +147,11 @@ def __init__(
 
         super().__init__(name="permutation", num_qubits=num_qubits, params=[pattern])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the Permutation gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+
         nq = len(self.pattern)
         mat = np.zeros((2**nq, 2**nq), dtype=dtype)
 
diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py
index 618041142227..1fd36e52e0c0 100644
--- a/qiskit/circuit/library/generalized_gates/unitary.py
+++ b/qiskit/circuit/library/generalized_gates/unitary.py
@@ -18,6 +18,7 @@
 import typing
 import numpy
 
+from qiskit import _numpy_compat
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.controlledgate import ControlledGate
 from qiskit.circuit.annotated_operation import AnnotatedOperation, ControlModifier
@@ -118,10 +119,10 @@ def __eq__(self, other):
             return False
         return matrix_equal(self.params[0], other.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
         """Return matrix for the unitary."""
-        # pylint: disable=unused-argument
-        return self.params[0]
+        dtype = self.params[0].dtype if dtype is None else dtype
+        return numpy.array(self.params[0], dtype=dtype, copy=copy)
 
     def inverse(self, annotated: bool = False):
         """Return the adjoint of the unitary."""
diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py
index a87504a97b36..2997d01ed487 100644
--- a/qiskit/circuit/library/hamiltonian_gate.py
+++ b/qiskit/circuit/library/hamiltonian_gate.py
@@ -21,6 +21,7 @@
 from numbers import Number
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.quantumregister import QuantumRegister
@@ -92,18 +93,22 @@ def __eq__(self, other):
         times_eq = self.params[1] == other.params[1]
         return operators_eq and times_eq
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return matrix for the unitary."""
-        # pylint: disable=unused-argument
         import scipy.linalg
 
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         try:
-            return scipy.linalg.expm(-1j * self.params[0] * float(self.params[1]))
+            time = float(self.params[1])
         except TypeError as ex:
             raise TypeError(
                 "Unable to generate Unitary matrix for "
                 "unbound t parameter {}".format(self.params[1])
             ) from ex
+        arr = scipy.linalg.expm(-1j * self.params[0] * time)
+        dtype = complex if dtype is None else dtype
+        return np.array(arr, dtype=dtype, copy=_numpy_compat.COPY_ONLY_IF_NEEDED)
 
     def inverse(self, annotated: bool = False):
         """Return the adjoint of the unitary."""
diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py
index 50576bf17ab4..ccd758e47241 100644
--- a/qiskit/circuit/library/standard_gates/global_phase.py
+++ b/qiskit/circuit/library/standard_gates/global_phase.py
@@ -69,10 +69,12 @@ def inverse(self, annotated: bool = False):
         """
         return GlobalPhaseGate(-self.params[0])
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the global_phase gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta = self.params[0]
-        return numpy.array([[numpy.exp(1j * theta)]], dtype=dtype)
+        return numpy.array([[numpy.exp(1j * theta)]], dtype=dtype or complex)
 
     def __eq__(self, other):
         if isinstance(other, GlobalPhaseGate):
diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py
index 179be025bcd0..6de0307dc798 100644
--- a/qiskit/circuit/library/standard_gates/p.py
+++ b/qiskit/circuit/library/standard_gates/p.py
@@ -140,8 +140,10 @@ def inverse(self, annotated: bool = False):
         """
         return PhaseGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the Phase gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         lam = float(self.params[0])
         return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype)
 
@@ -279,8 +281,10 @@ def inverse(self, annotated: bool = False):
         r"""Return inverted CPhase gate (:math:`CPhase(\lambda)^{\dagger} = CPhase(-\lambda)`)"""
         return CPhaseGate(-self.params[0], ctrl_state=self.ctrl_state)
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CPhase gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         eith = exp(1j * float(self.params[0]))
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py
index 3fc537baef90..9d4905e27866 100644
--- a/qiskit/circuit/library/standard_gates/r.py
+++ b/qiskit/circuit/library/standard_gates/r.py
@@ -93,8 +93,10 @@ def inverse(self, annotated: bool = False):
         """
         return RGate(-self.params[0], self.params[1])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the R gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, phi = float(self.params[0]), float(self.params[1])
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py
index 3483d5ebc956..eaa73cf87c91 100644
--- a/qiskit/circuit/library/standard_gates/rx.py
+++ b/qiskit/circuit/library/standard_gates/rx.py
@@ -120,8 +120,10 @@ def inverse(self, annotated: bool = False):
         """
         return RXGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RX gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         cos = math.cos(self.params[0] / 2)
         sin = math.sin(self.params[0] / 2)
         return numpy.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=dtype)
@@ -263,8 +265,10 @@ def inverse(self, annotated: bool = False):
         """
         return CRXGate(-self.params[0], ctrl_state=self.ctrl_state)
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CRX gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         isin = 1j * math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py
index 03e9d22dcc24..c4e35e53d55e 100644
--- a/qiskit/circuit/library/standard_gates/rxx.py
+++ b/qiskit/circuit/library/standard_gates/rxx.py
@@ -122,8 +122,10 @@ def inverse(self, annotated: bool = False):
         """
         return RXXGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the RXX gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta2 = float(self.params[0]) / 2
         cos = math.cos(theta2)
         isin = 1j * math.sin(theta2)
diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py
index b902887ee0e0..633a518bca77 100644
--- a/qiskit/circuit/library/standard_gates/ry.py
+++ b/qiskit/circuit/library/standard_gates/ry.py
@@ -119,8 +119,10 @@ def inverse(self, annotated: bool = False):
         """
         return RYGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RY gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         cos = math.cos(self.params[0] / 2)
         sin = math.sin(self.params[0] / 2)
         return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)
@@ -258,8 +260,10 @@ def inverse(self, annotated: bool = False):
         ."""
         return CRYGate(-self.params[0], ctrl_state=self.ctrl_state)
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CRY gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         sin = math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py
index 50ce9b0c4f73..98847b7b2182 100644
--- a/qiskit/circuit/library/standard_gates/ryy.py
+++ b/qiskit/circuit/library/standard_gates/ryy.py
@@ -122,8 +122,10 @@ def inverse(self, annotated: bool = False):
         """
         return RYYGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RYY gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta = float(self.params[0])
         cos = math.cos(theta / 2)
         isin = 1j * math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py
index c7311b4a6e59..3040f9568346 100644
--- a/qiskit/circuit/library/standard_gates/rz.py
+++ b/qiskit/circuit/library/standard_gates/rz.py
@@ -130,10 +130,12 @@ def inverse(self, annotated: bool = False):
         """
         return RZGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RZ gate."""
         import numpy as np
 
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         ilam2 = 0.5j * float(self.params[0])
         return np.array([[exp(-ilam2), 0], [0, exp(ilam2)]], dtype=dtype)
 
@@ -276,10 +278,12 @@ def inverse(self, annotated: bool = False):
         """
         return CRZGate(-self.params[0], ctrl_state=self.ctrl_state)
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CRZ gate."""
         import numpy
 
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         arg = 1j * float(self.params[0]) / 2
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py
index d59676663da1..1f930ab422df 100644
--- a/qiskit/circuit/library/standard_gates/rzx.py
+++ b/qiskit/circuit/library/standard_gates/rzx.py
@@ -166,10 +166,12 @@ def inverse(self, annotated: bool = False):
         """
         return RZXGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RZX gate."""
         import numpy
 
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         isin = 1j * math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py
index 3a00fb7b7395..5ca974764d32 100644
--- a/qiskit/circuit/library/standard_gates/rzz.py
+++ b/qiskit/circuit/library/standard_gates/rzz.py
@@ -130,10 +130,12 @@ def inverse(self, annotated: bool = False):
         """
         return RZZGate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RZZ gate."""
         import numpy
 
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         itheta2 = 1j * float(self.params[0]) / 2
         return numpy.array(
             [
diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py
index 81b48536f26b..3d631898850a 100644
--- a/qiskit/circuit/library/standard_gates/u.py
+++ b/qiskit/circuit/library/standard_gates/u.py
@@ -12,7 +12,7 @@
 
 """Two-pulse single-qubit gate."""
 import cmath
-import copy
+import copy as _copy
 import math
 from cmath import exp
 from typing import Optional, Union
@@ -136,8 +136,10 @@ def control(
             )
         return gate
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the U gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, phi, lam = (float(param) for param in self.params)
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
@@ -146,7 +148,7 @@ def __array__(self, dtype=complex):
                 [cos, -exp(1j * lam) * sin],
                 [exp(1j * phi) * sin, exp(1j * (phi + lam)) * cos],
             ],
-            dtype=dtype,
+            dtype=dtype or complex,
         )
 
     def __eq__(self, other):
@@ -337,8 +339,10 @@ def inverse(self, annotated: bool = False):
             ctrl_state=self.ctrl_state,
         )
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, phi, lam, gamma = (float(param) for param in self.params)
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
@@ -372,5 +376,5 @@ def __deepcopy__(self, memo=None):
         # assuming that `params` will be a view onto the base gate's `_params`.
         memo = memo if memo is not None else {}
         out = super().__deepcopy__(memo)
-        out._params = copy.deepcopy(out._params, memo)
+        out._params = _copy.deepcopy(out._params, memo)
         return out
diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py
index b92bea51a26f..1d59cabae1f6 100644
--- a/qiskit/circuit/library/standard_gates/u1.py
+++ b/qiskit/circuit/library/standard_gates/u1.py
@@ -160,8 +160,10 @@ def inverse(self, annotated: bool = False):
         """
         return U1Gate(-self.params[0])
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the U1 gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         lam = float(self.params[0])
         return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=dtype)
 
@@ -304,8 +306,10 @@ def inverse(self, annotated: bool = False):
         """
         return CU1Gate(-self.params[0], ctrl_state=self.ctrl_state)
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU1 gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         eith = exp(1j * float(self.params[0]))
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py
index 021a38f4daeb..c8e4de96efec 100644
--- a/qiskit/circuit/library/standard_gates/u2.py
+++ b/qiskit/circuit/library/standard_gates/u2.py
@@ -127,8 +127,10 @@ def inverse(self, annotated: bool = False):
         """
         return U2Gate(-self.params[1] - pi, -self.params[0] + pi)
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the U2 gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         isqrt2 = 1 / sqrt(2)
         phi, lam = self.params
         phi, lam = float(phi), float(lam)
@@ -137,5 +139,5 @@ def __array__(self, dtype=complex):
                 [isqrt2, -exp(1j * lam) * isqrt2],
                 [exp(1j * phi) * isqrt2, exp(1j * (phi + lam)) * isqrt2],
             ],
-            dtype=dtype,
+            dtype=dtype or complex,
         )
diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py
index c92a48ab52b6..62c1e33b9628 100644
--- a/qiskit/circuit/library/standard_gates/u3.py
+++ b/qiskit/circuit/library/standard_gates/u3.py
@@ -149,8 +149,10 @@ def _define(self):
         qc.u(self.params[0], self.params[1], self.params[2], 0)
         self.definition = qc
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the U3 gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, phi, lam = self.params
         theta, phi, lam = float(theta), float(phi), float(lam)
         cos = math.cos(theta / 2)
@@ -160,7 +162,7 @@ def __array__(self, dtype=complex):
                 [cos, -exp(1j * lam) * sin],
                 [exp(1j * phi) * sin, exp(1j * (phi + lam)) * cos],
             ],
-            dtype=dtype,
+            dtype=dtype or complex,
         )
 
 
@@ -305,8 +307,10 @@ def inverse(self, annotated: bool = False):
             -self.params[0], -self.params[2], -self.params[1], ctrl_state=self.ctrl_state
         )
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU3 gate."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, phi, lam = self.params
         theta, phi, lam = float(theta), float(phi), float(lam)
         cos = math.cos(theta / 2)
@@ -319,7 +323,7 @@ def __array__(self, dtype=complex):
                     [0, 0, 1, 0],
                     [0, exp(1j * phi) * sin, 0, exp(1j * (phi + lam)) * cos],
                 ],
-                dtype=dtype,
+                dtype=dtype or complex,
             )
         else:
             return numpy.array(
@@ -329,7 +333,7 @@ def __array__(self, dtype=complex):
                     [exp(1j * phi) * sin, 0, exp(1j * (phi + lam)) * cos, 0],
                     [0, 0, 0, 1],
                 ],
-                dtype=dtype,
+                dtype=dtype or complex,
             )
 
 
diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
index 387a23ad058a..4bf4ab80eca2 100644
--- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
@@ -169,8 +169,10 @@ def inverse(self, annotated: bool = False):
         theta, beta = self.params
         return XXMinusYYGate(-theta, beta)
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Gate matrix."""
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         theta, beta = self.params
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
index b69ba49de30d..a7b62175f207 100644
--- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
@@ -15,6 +15,9 @@
 from cmath import exp
 from math import pi
 from typing import Optional
+
+import numpy
+
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.parameterexpression import ParameterValueType
@@ -167,10 +170,10 @@ def inverse(self, annotated: bool = False):
         """
         return XXPlusYYGate(-self.params[0], self.params[1])
 
-    def __array__(self, dtype=complex):
+    def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the XX+YY gate."""
-        import numpy
-
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
         half_theta = float(self.params[0]) / 2
         beta = float(self.params[1])
         cos = math.cos(half_theta)
diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py
index 6a990465c5bd..51d1ded1500c 100644
--- a/qiskit/primitives/backend_sampler_v2.py
+++ b/qiskit/primitives/backend_sampler_v2.py
@@ -268,7 +268,7 @@ def _memory_array(results: list[list[str]], num_bytes: int) -> NDArray[np.uint8]
             # no measure in a circuit
             data = np.zeros((len(memory), num_bytes), dtype=np.uint8)
         lst.append(data)
-    ary = np.array(lst, copy=False)
+    ary = np.asarray(lst)
     return np.unpackbits(ary, axis=-1, bitorder="big")
 
 
diff --git a/qiskit/primitives/containers/observables_array.py b/qiskit/primitives/containers/observables_array.py
index 0d0322dc6a3c..21c415d75899 100644
--- a/qiskit/primitives/containers/observables_array.py
+++ b/qiskit/primitives/containers/observables_array.py
@@ -101,10 +101,10 @@ def tolist(self) -> list:
         """Convert to a nested list"""
         return self._array.tolist()
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Convert to an Numpy.ndarray"""
         if dtype is None or dtype == object:
-            return self._array
+            return self._array.copy() if copy else self._array
         raise ValueError("Type must be 'None' or 'object'")
 
     @overload
diff --git a/qiskit/primitives/containers/shape.py b/qiskit/primitives/containers/shape.py
index 952916cd67dc..6d893f46c13f 100644
--- a/qiskit/primitives/containers/shape.py
+++ b/qiskit/primitives/containers/shape.py
@@ -85,7 +85,7 @@ def array_coerce(arr: ArrayLike | Shaped) -> NDArray | Shaped:
     """
     if isinstance(arr, Shaped):
         return arr
-    return np.array(arr, copy=False)
+    return np.asarray(arr)
 
 
 def _flatten_to_ints(arg: ShapeInput) -> Iterable[int]:
diff --git a/qiskit/quantum_info/operators/channel/chi.py b/qiskit/quantum_info/operators/channel/chi.py
index 7cd5fce5258f..ee0ddaa45385 100644
--- a/qiskit/quantum_info/operators/channel/chi.py
+++ b/qiskit/quantum_info/operators/channel/chi.py
@@ -16,10 +16,11 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 import math
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.instruction import Instruction
 from qiskit.exceptions import QiskitError
@@ -131,10 +132,9 @@ def __init__(
             raise QiskitError("Input is not an n-qubit Chi matrix.")
         super().__init__(chi_mat, num_qubits=num_qubits)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     @property
     def _bipartite_shape(self):
@@ -181,7 +181,7 @@ def expand(self, other: Chi) -> Chi:
 
     @classmethod
     def _tensor(cls, a, b):
-        ret = copy.copy(a)
+        ret = _copy.copy(a)
         ret._op_shape = a._op_shape.tensor(b._op_shape)
         ret._data = np.kron(a._data, b.data)
         return ret
diff --git a/qiskit/quantum_info/operators/channel/choi.py b/qiskit/quantum_info/operators/channel/choi.py
index afd8e4fca6f9..1c9579e45602 100644
--- a/qiskit/quantum_info/operators/channel/choi.py
+++ b/qiskit/quantum_info/operators/channel/choi.py
@@ -16,10 +16,11 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 import math
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.instruction import Instruction
 from qiskit.exceptions import QiskitError
@@ -134,10 +135,9 @@ def __init__(
             choi_mat = _to_choi(rep, data._data, input_dim, output_dim)
         super().__init__(choi_mat, op_shape=op_shape)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     @property
     def _bipartite_shape(self):
@@ -152,12 +152,12 @@ def _evolve(self, state, qargs=None):
     # ---------------------------------------------------------------------
 
     def conjugate(self):
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.conj(self._data)
         return ret
 
     def transpose(self):
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._op_shape = self._op_shape.transpose()
         # Make bipartite matrix
         d_in, d_out = self.dim
@@ -206,7 +206,7 @@ def expand(self, other: Choi) -> Choi:
 
     @classmethod
     def _tensor(cls, a, b):
-        ret = copy.copy(a)
+        ret = _copy.copy(a)
         ret._op_shape = a._op_shape.tensor(b._op_shape)
         ret._data = _bipartite_tensor(
             a._data, b.data, shape1=a._bipartite_shape, shape2=b._bipartite_shape
diff --git a/qiskit/quantum_info/operators/channel/ptm.py b/qiskit/quantum_info/operators/channel/ptm.py
index 1bdf1b5ef235..84db071121f3 100644
--- a/qiskit/quantum_info/operators/channel/ptm.py
+++ b/qiskit/quantum_info/operators/channel/ptm.py
@@ -16,10 +16,11 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 import math
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.instruction import Instruction
 from qiskit.exceptions import QiskitError
@@ -133,10 +134,9 @@ def __init__(
             raise QiskitError("Input is not an n-qubit Pauli transfer matrix.")
         super().__init__(ptm, num_qubits=num_qubits)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     @property
     def _bipartite_shape(self):
@@ -194,7 +194,7 @@ def expand(self, other: PTM) -> PTM:
 
     @classmethod
     def _tensor(cls, a, b):
-        ret = copy.copy(a)
+        ret = _copy.copy(a)
         ret._op_shape = a._op_shape.tensor(b._op_shape)
         ret._data = np.kron(a._data, b.data)
         return ret
diff --git a/qiskit/quantum_info/operators/channel/superop.py b/qiskit/quantum_info/operators/channel/superop.py
index 0d7116ef5069..19867696ec6a 100644
--- a/qiskit/quantum_info/operators/channel/superop.py
+++ b/qiskit/quantum_info/operators/channel/superop.py
@@ -15,12 +15,13 @@
 
 from __future__ import annotations
 
-import copy
+import copy as _copy
 import math
 from typing import TYPE_CHECKING
 
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.exceptions import QiskitError
@@ -127,10 +128,9 @@ def __init__(
         # Initialize QuantumChannel
         super().__init__(super_mat, op_shape=op_shape)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     @property
     def _tensor_shape(self):
@@ -149,18 +149,18 @@ def _bipartite_shape(self):
     # ---------------------------------------------------------------------
 
     def conjugate(self):
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.conj(self._data)
         return ret
 
     def transpose(self):
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.transpose(self._data)
         ret._op_shape = self._op_shape.transpose()
         return ret
 
     def adjoint(self):
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.conj(np.transpose(self._data))
         ret._op_shape = self._op_shape.transpose()
         return ret
@@ -177,7 +177,7 @@ def expand(self, other: SuperOp) -> SuperOp:
 
     @classmethod
     def _tensor(cls, a, b):
-        ret = copy.copy(a)
+        ret = _copy.copy(a)
         ret._op_shape = a._op_shape.tensor(b._op_shape)
         ret._data = _bipartite_tensor(
             a._data, b.data, shape1=a._bipartite_shape, shape2=b._bipartite_shape
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py
index af15e0ed3ae5..4f49879063ec 100644
--- a/qiskit/quantum_info/operators/dihedral/dihedral.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral.py
@@ -357,10 +357,11 @@ def _from_circuit(self, circuit):
         _append_circuit(elem, circuit)
         return elem
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.to_matrix(), dtype=dtype)
-        return self.to_matrix()
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     def to_matrix(self):
         """Convert operator to Numpy matrix."""
diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
index 90d40b93cc50..d119a3812493 100644
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -16,13 +16,14 @@
 
 from __future__ import annotations
 
-import copy
+import copy as _copy
 import re
 from numbers import Number
 from typing import TYPE_CHECKING
 
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.library.standard_gates import HGate, IGate, SGate, TGate, XGate, YGate, ZGate
 from qiskit.circuit.operation import Operation
@@ -117,10 +118,9 @@ def __init__(
             shape=self._data.shape,
         )
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     def __repr__(self):
         prefix = "Operator("
@@ -447,13 +447,13 @@ def to_instruction(self):
 
     def conjugate(self):
         # Make a shallow copy and update array
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.conj(self._data)
         return ret
 
     def transpose(self):
         # Make a shallow copy and update array
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.transpose(self._data)
         ret._op_shape = self._op_shape.transpose()
         return ret
@@ -523,7 +523,7 @@ def power(self, n: float) -> Operator:
         """
         if self.input_dims() != self.output_dims():
             raise QiskitError("Can only power with input_dims = output_dims.")
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         if isinstance(n, int):
             ret._data = np.linalg.matrix_power(self.data, n)
         else:
@@ -550,7 +550,7 @@ def expand(self, other: Operator) -> Operator:
 
     @classmethod
     def _tensor(cls, a, b):
-        ret = copy.copy(a)
+        ret = _copy.copy(a)
         ret._op_shape = a._op_shape.tensor(b._op_shape)
         ret._data = np.kron(a.data, b.data)
         return ret
@@ -585,7 +585,7 @@ def _add(self, other, qargs=None):
         self._op_shape._validate_add(other._op_shape, qargs)
         other = ScalarOp._pad_with_identity(self, other, qargs)
 
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = self.data + other.data
         return ret
 
@@ -603,7 +603,7 @@ def _multiply(self, other):
         """
         if not isinstance(other, Number):
             raise QiskitError("other is not a number")
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = other * self._data
         return ret
 
@@ -643,7 +643,7 @@ def reverse_qargs(self) -> Operator:
         Returns:
             Operator: the operator with reversed subsystem order.
         """
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
         axes = axes + tuple(len(axes) + i for i in axes)
         ret._data = np.reshape(
diff --git a/qiskit/quantum_info/operators/scalar_op.py b/qiskit/quantum_info/operators/scalar_op.py
index d856a39c2a20..38f36193739b 100644
--- a/qiskit/quantum_info/operators/scalar_op.py
+++ b/qiskit/quantum_info/operators/scalar_op.py
@@ -15,7 +15,7 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 from numbers import Number
 import numpy as np
 
@@ -52,10 +52,11 @@ def __init__(self, dims: int | tuple | None = None, coeff: Number = 1):
         self._coeff = coeff
         super().__init__(input_dims=dims, output_dims=dims)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.to_matrix(), dtype=dtype)
-        return self.to_matrix()
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("could not produce matrix without calculation")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     def __repr__(self):
         return f"ScalarOp({self.input_dims()}, coeff={self.coeff})"
@@ -104,7 +105,7 @@ def compose(self, other: ScalarOp, qargs: list | None = None, front: bool = Fals
         # If other is also an ScalarOp we only need to
         # update the coefficient and dimensions
         if isinstance(other, ScalarOp):
-            ret = copy.copy(self)
+            ret = _copy.copy(self)
             ret._coeff = self.coeff * other.coeff
             ret._op_shape = new_shape
             return ret
@@ -112,7 +113,7 @@ def compose(self, other: ScalarOp, qargs: list | None = None, front: bool = Fals
         # If we are composing on the full system we return the
         # other operator with reshaped dimensions
         if qargs is None:
-            ret = copy.copy(other)
+            ret = _copy.copy(other)
             ret._op_shape = new_shape
             # Other operator might not support scalar multiplication
             # so we treat the identity as a special case to avoid a
@@ -148,7 +149,7 @@ def tensor(self, other: ScalarOp) -> ScalarOp:
             other = Operator(other)
 
         if isinstance(other, ScalarOp):
-            ret = copy.copy(self)
+            ret = _copy.copy(self)
             ret._coeff = self.coeff * other.coeff
             ret._op_shape = self._op_shape.tensor(other._op_shape)
             return ret
@@ -160,7 +161,7 @@ def expand(self, other: ScalarOp) -> ScalarOp:
             other = Operator(other)
 
         if isinstance(other, ScalarOp):
-            ret = copy.copy(self)
+            ret = _copy.copy(self)
             ret._coeff = self.coeff * other.coeff
             ret._op_shape = self._op_shape.expand(other._op_shape)
             return ret
diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py
index f0d30cf59f0c..47ef8b629dd6 100644
--- a/qiskit/quantum_info/operators/symplectic/clifford.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford.py
@@ -122,10 +122,11 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
     _COMPOSE_PHASE_LOOKUP = None
     _COMPOSE_1Q_LOOKUP = None
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.to_matrix(), dtype=dtype)
-        return self.to_matrix()
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     def __init__(self, data, validate=True, copy=True):
         """Initialize an operator object."""
@@ -164,8 +165,17 @@ def __init__(self, data, validate=True, copy=True):
 
         # Initialize StabilizerTable directly from the data
         else:
-            if isinstance(data, (list, np.ndarray)) and np.asarray(data, dtype=bool).ndim == 2:
-                data = np.array(data, dtype=bool, copy=copy)
+            if (
+                isinstance(data, (list, np.ndarray))
+                and (data_asarray := np.asarray(data, dtype=bool)).ndim == 2
+            ):
+                # This little dance is to avoid Numpy 1/2 incompatiblities between the availability
+                # and meaning of the 'copy' argument in 'array' and 'asarray', when the input needs
+                # its dtype converting.  'asarray' prefers to return 'self' if possible in both.
+                if copy and np.may_share_memory(data, data_asarray):
+                    data = data_asarray.copy()
+                else:
+                    data = data_asarray
                 if data.shape[0] == data.shape[1]:
                     self.tableau = self._stack_table_phase(
                         data, np.zeros(data.shape[0], dtype=bool)
diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index 1bdff0cf8fea..e1bcfa29ebcb 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli.py
@@ -222,10 +222,11 @@ def __str__(self):
             return front + "..."
         return self.to_label()
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.to_matrix(), dtype=dtype)
-        return self.to_matrix()
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     @classmethod
     def set_truncation(cls, val: int):
diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py
index 1bb9ae3d1ada..3d348d236387 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli_list.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py
@@ -148,14 +148,15 @@ def settings(self):
         """Return settings."""
         return {"data": self.to_labels()}
 
-    def __array__(self, dtype=None):
+    def __array__(self, dtype=None, copy=None):
         """Convert to numpy array"""
-        # pylint: disable=unused-argument
+        if copy is False:
+            raise ValueError("cannot provide a matrix without calculation")
         shape = (len(self),) + 2 * (2**self.num_qubits,)
         ret = np.zeros(shape, dtype=complex)
         for i, mat in enumerate(self.matrix_iter()):
             ret[i] = mat
-        return ret
+        return ret if dtype is None else ret.astype(dtype, copy=False)
 
     @staticmethod
     def _from_paulis(data):
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index 0d29cd098dc3..dc445509b0e3 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -142,7 +142,12 @@ def __init__(
         if coeffs is None:
             coeffs = np.ones(pauli_list.size, dtype=complex)
         else:
-            coeffs = np.array(coeffs, copy=copy, dtype=dtype)
+            coeffs_asarray = np.asarray(coeffs, dtype=dtype)
+            coeffs = (
+                coeffs_asarray.copy()
+                if copy and np.may_share_memory(coeffs, coeffs_asarray)
+                else coeffs_asarray
+            )
 
         if ignore_pauli_phase:
             # Fast path used in copy operations, where the phase of the PauliList is already known
@@ -166,10 +171,11 @@ def __init__(
         # Initialize LinearOp
         super().__init__(num_qubits=self._pauli_list.num_qubits)
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.to_matrix(), dtype=dtype)
-        return self.to_matrix()
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     def __repr__(self):
         prefix = "SparsePauliOp("
diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py
index 07cc65685745..1c66d8bcf5cf 100644
--- a/qiskit/quantum_info/states/densitymatrix.py
+++ b/qiskit/quantum_info/states/densitymatrix.py
@@ -15,10 +15,11 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 from numbers import Number
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.instruction import Instruction
 from qiskit.exceptions import QiskitError
@@ -110,10 +111,9 @@ def __init__(
             raise QiskitError("Invalid DensityMatrix input: not a square matrix.")
         super().__init__(op_shape=OpShape.auto(shape=self._data.shape, dims_l=dims, dims_r=dims))
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     def __eq__(self, other):
         return super().__eq__(other) and np.allclose(
@@ -241,7 +241,7 @@ def tensor(self, other: DensityMatrix) -> DensityMatrix:
         """
         if not isinstance(other, DensityMatrix):
             other = DensityMatrix(other)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.kron(self._data, other._data)
         ret._op_shape = self._op_shape.tensor(other._op_shape)
         return ret
@@ -260,7 +260,7 @@ def expand(self, other: DensityMatrix) -> DensityMatrix:
         """
         if not isinstance(other, DensityMatrix):
             other = DensityMatrix(other)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = np.kron(other._data, self._data)
         ret._op_shape = self._op_shape.expand(other._op_shape)
         return ret
@@ -281,7 +281,7 @@ def _add(self, other):
         if not isinstance(other, DensityMatrix):
             other = DensityMatrix(other)
         self._op_shape._validate_add(other._op_shape)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = self.data + other.data
         return ret
 
@@ -299,7 +299,7 @@ def _multiply(self, other):
         """
         if not isinstance(other, Number):
             raise QiskitError("other is not a number")
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = other * self.data
         return ret
 
@@ -356,7 +356,7 @@ def reverse_qargs(self) -> DensityMatrix:
         Returns:
             DensityMatrix: the state with reversed subsystem order.
         """
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
         axes = axes + tuple(len(axes) + i for i in axes)
         ret._data = np.reshape(
@@ -523,7 +523,7 @@ def reset(self, qargs: list[int] | None = None) -> DensityMatrix:
         """
         if qargs is None:
             # Resetting all qubits does not require sampling or RNG
-            ret = copy.copy(self)
+            ret = _copy.copy(self)
             state = np.zeros(self._op_shape.shape, dtype=complex)
             state[0, 0] = 1
             ret._data = state
@@ -715,7 +715,7 @@ def _evolve_operator(self, other, qargs=None):
         new_shape._dims_r = new_shape._dims_l
         new_shape._num_qargs_r = new_shape._num_qargs_l
 
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         if qargs is None:
             # Evolution on full matrix
             op_mat = other.data
@@ -792,7 +792,7 @@ def _evolve_instruction(self, obj, qargs=None):
         """Return a new statevector by applying an instruction."""
         if isinstance(obj, QuantumCircuit):
             obj = obj.to_instruction()
-        vec = copy.copy(self)
+        vec = _copy.copy(self)
         vec._append_instruction(obj, qargs=qargs)
         return vec
 
diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py
index b13ccde21743..df39ba42f915 100644
--- a/qiskit/quantum_info/states/statevector.py
+++ b/qiskit/quantum_info/states/statevector.py
@@ -14,13 +14,14 @@
 Statevector quantum state class.
 """
 from __future__ import annotations
-import copy
+import copy as _copy
 import math
 import re
 from numbers import Number
 
 import numpy as np
 
+from qiskit import _numpy_compat
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.instruction import Instruction
 from qiskit.exceptions import QiskitError
@@ -104,10 +105,9 @@ def __init__(
                 raise QiskitError("Invalid input: not a vector or column-vector.")
         super().__init__(op_shape=OpShape.auto(shape=shape, dims_l=dims, num_qubits_r=0))
 
-    def __array__(self, dtype=None):
-        if dtype:
-            return np.asarray(self.data, dtype=dtype)
-        return self.data
+    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
+        dtype = self.data.dtype if dtype is None else dtype
+        return np.array(self.data, dtype=dtype, copy=copy)
 
     def __eq__(self, other):
         return super().__eq__(other) and np.allclose(
@@ -277,7 +277,7 @@ def tensor(self, other: Statevector) -> Statevector:
         """
         if not isinstance(other, Statevector):
             other = Statevector(other)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._op_shape = self._op_shape.tensor(other._op_shape)
         ret._data = np.kron(self._data, other._data)
         return ret
@@ -318,7 +318,7 @@ def expand(self, other: Statevector) -> Statevector:
         """
         if not isinstance(other, Statevector):
             other = Statevector(other)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._op_shape = self._op_shape.expand(other._op_shape)
         ret._data = np.kron(other._data, self._data)
         return ret
@@ -339,7 +339,7 @@ def _add(self, other):
         if not isinstance(other, Statevector):
             other = Statevector(other)
         self._op_shape._validate_add(other._op_shape)
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = self.data + other.data
         return ret
 
@@ -357,7 +357,7 @@ def _multiply(self, other):
         """
         if not isinstance(other, Number):
             raise QiskitError("other is not a number")
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         ret._data = other * self.data
         return ret
 
@@ -382,7 +382,7 @@ def evolve(
             qargs = getattr(other, "qargs", None)
 
         # Get return vector
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
 
         # Evolution by a circuit or instruction
         if isinstance(other, QuantumCircuit):
@@ -448,7 +448,7 @@ def reverse_qargs(self) -> Statevector:
         Returns:
             Statevector: the Statevector with reversed subsystem order.
         """
-        ret = copy.copy(self)
+        ret = _copy.copy(self)
         axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
         ret._data = np.reshape(
             np.transpose(np.reshape(self.data, self._op_shape.tensor_shape), axes),
@@ -619,7 +619,7 @@ def reset(self, qargs: list[int] | None = None) -> Statevector:
         """
         if qargs is None:
             # Resetting all qubits does not require sampling or RNG
-            ret = copy.copy(self)
+            ret = _copy.copy(self)
             state = np.zeros(self._op_shape.shape, dtype=complex)
             state[0] = 1
             ret._data = state
diff --git a/qiskit/visualization/array.py b/qiskit/visualization/array.py
index b076e38b174d..3a8ef2917156 100644
--- a/qiskit/visualization/array.py
+++ b/qiskit/visualization/array.py
@@ -33,7 +33,7 @@ def _num_to_latex(raw_value, decimals=15, first_term=True, coefficient=False):
     """
     import sympy  # runtime import
 
-    raw_value = np.around(raw_value, decimals=decimals)
+    raw_value = np.around(raw_value, decimals=decimals).item()
     value = sympy.nsimplify(raw_value, rational=False)
 
     if isinstance(value, sympy.core.numbers.Rational) and value.denominator > 50:
diff --git a/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml b/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml
new file mode 100644
index 000000000000..3595f2f936bd
--- /dev/null
+++ b/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    This release of Qiskit finalizes support for NumPy 2.0.  Qiskit will continue to support both
+    Numpy 1.x and 2.x for the foreseeable future.
diff --git a/requirements.txt b/requirements.txt
index 0a4f0f32c5a9..539f9587994d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
 rustworkx>=0.14.0
-numpy>=1.17,<2
+numpy>=1.17,<3
 scipy>=1.5
 sympy>=1.3
 dill>=0.3
 python-dateutil>=2.8.0
 stevedore>=3.0.0
 typing-extensions
-symengine>=0.11
\ No newline at end of file
+symengine>=0.11
diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py
index 6b974df902cd..70330085b1ab 100644
--- a/test/python/providers/test_backend_v2.py
+++ b/test/python/providers/test_backend_v2.py
@@ -19,6 +19,7 @@
 
 from ddt import ddt, data
 
+from numpy.testing import assert_array_max_ulp
 from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister
 from qiskit.circuit.library.standard_gates import (
     CXGate,
@@ -66,9 +67,11 @@ def assertMatchesTargetConstraints(self, tqc, target):
     def test_qubit_properties(self):
         """Test that qubit properties are returned as expected."""
         props = self.backend.qubit_properties([1, 0])
-        self.assertEqual([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
-        self.assertEqual([0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props])
-        self.assertEqual([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
+        assert_array_max_ulp([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
+        assert_array_max_ulp(
+            [0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props]
+        )
+        assert_array_max_ulp([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
 
     def test_legacy_qubit_properties(self):
         """Test that qubit props work for backends not using properties in target."""
@@ -82,9 +85,11 @@ def qubit_properties(self, qubit):
                 return [self.target.qubit_properties[i] for i in qubit]
 
         props = FakeBackendV2LegacyQubitProps(num_qubits=2, seed=42).qubit_properties([1, 0])
-        self.assertEqual([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
-        self.assertEqual([0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props])
-        self.assertEqual([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
+        assert_array_max_ulp([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
+        assert_array_max_ulp(
+            [0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props]
+        )
+        assert_array_max_ulp([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
 
     def test_no_qubit_properties_raises(self):
         """Ensure that if no qubit properties are defined we raise correctly."""
diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py
index 6460847b2e81..5f11ede3a3e8 100644
--- a/test/python/transpiler/test_dynamical_decoupling.py
+++ b/test/python/transpiler/test_dynamical_decoupling.py
@@ -447,7 +447,9 @@ class Echo(Gate):
             representation to satisfy PadDynamicalDecoupling's check.
             """
 
-            def __array__(self, dtype=None):
+            def __array__(self, dtype=None, copy=None):
+                if copy is False:
+                    raise ValueError("cannot produce matrix without calculation")
                 return np.eye(2, dtype=dtype)
 
         # A gate with one unbound and one bound parameter to leave in the final

From 136548c9d0fb1f9f05c473296e03d7d6cf3a82e1 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 25 Apr 2024 22:45:10 +0100
Subject: [PATCH 027/179] Fix pickling of `NLayout` (#12298)

Previously the module of this was set incorrectly (stemming from its
move in gh-9064), at which point the `__getstate__`/`__setstate__`
pickling wouldn't work correctly any more.  Also, however, there was no
`__getnewargs__` and `new` didn't have a zero-argument form, so this
wouldn't have worked either.
---
 crates/accelerate/src/nlayout.rs      | 16 ++++-----
 test/python/transpiler/test_layout.py | 51 +++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 8 deletions(-)

diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs
index 53c0a468e822..1a0b73b25fed 100644
--- a/crates/accelerate/src/nlayout.rs
+++ b/crates/accelerate/src/nlayout.rs
@@ -91,7 +91,7 @@ impl VirtualQubit {
 ///         physical qubit index on the coupling graph.
 ///     logical_qubits (int): The number of logical qubits in the layout
 ///     physical_qubits (int): The number of physical qubits in the layout
-#[pyclass(module = "qiskit._accelerate.stochastic_swap")]
+#[pyclass(module = "qiskit._accelerate.nlayout")]
 #[derive(Clone, Debug)]
 pub struct NLayout {
     virt_to_phys: Vec,
@@ -117,13 +117,13 @@ impl NLayout {
         res
     }
 
-    fn __getstate__(&self) -> (Vec, Vec) {
-        (self.virt_to_phys.clone(), self.phys_to_virt.clone())
-    }
-
-    fn __setstate__(&mut self, state: (Vec, Vec)) {
-        self.virt_to_phys = state.0;
-        self.phys_to_virt = state.1;
+    fn __reduce__(&self, py: Python) -> PyResult> {
+        Ok((
+            py.get_type_bound::()
+                .getattr("from_virtual_to_physical")?,
+            (self.virt_to_phys.to_object(py),),
+        )
+            .into_py(py))
     }
 
     /// Return the layout mapping.
diff --git a/test/python/transpiler/test_layout.py b/test/python/transpiler/test_layout.py
index a77b8e55370a..d90645289542 100644
--- a/test/python/transpiler/test_layout.py
+++ b/test/python/transpiler/test_layout.py
@@ -13,12 +13,14 @@
 """Tests the layout object"""
 
 import copy
+import pickle
 import unittest
 import numpy
 
 from qiskit.circuit import QuantumRegister, Qubit
 from qiskit.transpiler.layout import Layout
 from qiskit.transpiler.exceptions import LayoutError
+from qiskit._accelerate.nlayout import NLayout
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -511,5 +513,54 @@ def test_to_permutation(self):
         self.assertEqual(permutation, [1, 2, 0])
 
 
+class TestNLayout(QiskitTestCase):
+    """This is a private class, so mostly doesn't need direct tests."""
+
+    def test_pickle(self):
+        """Test that the layout roundtrips through pickle."""
+        v2p = [3, 5, 1, 2, 0, 4]
+        size = len(v2p)
+        layout = NLayout.from_virtual_to_physical(v2p)
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        roundtripped = pickle.loads(pickle.dumps(layout))
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], v2p)
+
+        # No changes to `layout`.
+        roundtripped.swap_virtual(0, 1)
+        expected = [5, 3, 1, 2, 0, 4]
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], expected)
+
+    def test_copy(self):
+        """Test that the layout roundtrips through copy."""
+        v2p = [3, 5, 1, 2, 0, 4]
+        size = len(v2p)
+        layout = NLayout.from_virtual_to_physical(v2p)
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        roundtripped = copy.copy(layout)
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], v2p)
+
+        # No changes to `layout`.
+        roundtripped.swap_virtual(0, 1)
+        expected = [5, 3, 1, 2, 0, 4]
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], expected)
+
+    def test_deepcopy(self):
+        """Test that the layout roundtrips through deepcopy."""
+        v2p = [3, 5, 1, 2, 0, 4]
+        size = len(v2p)
+        layout = NLayout.from_virtual_to_physical(v2p)
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        roundtripped = copy.deepcopy(layout)
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], v2p)
+
+        # No changes to `layout`.
+        roundtripped.swap_virtual(0, 1)
+        expected = [5, 3, 1, 2, 0, 4]
+        self.assertEqual([layout.virtual_to_physical(x) for x in range(size)], v2p)
+        self.assertEqual([roundtripped.virtual_to_physical(x) for x in range(size)], expected)
+
+
 if __name__ == "__main__":
     unittest.main()

From ce8bed670ab4e2a14574ab0956f2685e25e90e6f Mon Sep 17 00:00:00 2001
From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Date: Fri, 26 Apr 2024 13:15:26 +0300
Subject: [PATCH 028/179] Extend Clifford class docs (#12177)

* extend clifford docs

* mimor fix

* minor fix

* update docs following comment

* update following review
---
 .../operators/symplectic/clifford.py          | 20 +++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py
index 47ef8b629dd6..9a5e8732ae68 100644
--- a/qiskit/quantum_info/operators/symplectic/clifford.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford.py
@@ -37,7 +37,23 @@
 
 
 class Clifford(BaseOperator, AdjointMixin, Operation):
-    """An N-qubit unitary operator from the Clifford group.
+    r"""
+    An N-qubit unitary operator from the Clifford group.
+
+    An N-qubit Clifford operator takes Paulis to Paulis via conjugation
+    (up to a global phase). More precisely, the Clifford group :math:`\mathcal{C}_N`
+    is defined as
+
+     .. math::
+
+        \mathcal{C}_N = \{ U \in U(2^N) | U \mathcal{P}_N U^{\dagger} = \mathcal{P}_N \} / U(1)
+
+     where :math:`\mathcal{P}_N` is the Pauli group on :math:`N` qubits
+     that is generated by single-qubit Pauli operators,
+     and :math:`U` is a unitary operator in the unitary group
+     :math:`U(2^N)` representing operations on :math:`N` qubits.
+     :math:`\mathcal{C}_N` is the quotient group by the subgroup of
+     scalar unitary matrices :math:`U(1)`.
 
     **Representation**
 
@@ -91,7 +107,7 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
     :class:`~qiskit.circuit.library.SGate`, :class:`~qiskit.circuit.library.SdgGate`,
     :class:`~qiskit.circuit.library.SXGate`, :class:`~qiskit.circuit.library.SXdgGate`,
     :class:`~qiskit.circuit.library.CXGate`, :class:`~qiskit.circuit.library.CZGate`,
-    :class:`~qiskit.circuit.library.CYGate`, :class:`~qiskit.circuit.library.DXGate`,
+    :class:`~qiskit.circuit.library.CYGate`, :class:`~qiskit.circuit.library.DCXGate`,
     :class:`~qiskit.circuit.library.SwapGate`, :class:`~qiskit.circuit.library.iSwapGate`,
     :class:`~qiskit.circuit.library.ECRGate`, :class:`~qiskit.circuit.library.LinearFunction`,
     :class:`~qiskit.circuit.library.PermutationGate`.

From 80b54b966cd8b5111ce2be2e4e1b902f9b8a04cf Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Fri, 26 Apr 2024 06:19:21 -0400
Subject: [PATCH 029/179] Removing use-dict-literal from lint exclusions and
 updates (#12297)

* removing use-dict-literal from lint exclusions and updates

* removing unnecessary destructuring and restructuring in test_result.py
---
 pyproject.toml                    |  1 -
 qiskit/assembler/disassemble.py   | 11 ++++----
 qiskit/compiler/assembler.py      | 44 +++++++++++++++----------------
 test/python/result/test_result.py | 34 ++++++++++++------------
 4 files changed, 44 insertions(+), 46 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index be4cc7f2d9ea..40c6701fd05a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -232,7 +232,6 @@ disable = [
     "unnecessary-lambda-assignment",
     "unspecified-encoding",
     "unsupported-assignment-operation",
-    "use-dict-literal",
     "use-implicit-booleaness-not-comparison",
 ]
 
diff --git a/qiskit/assembler/disassemble.py b/qiskit/assembler/disassemble.py
index e777f4050dda..c94b108c4b25 100644
--- a/qiskit/assembler/disassemble.py
+++ b/qiskit/assembler/disassemble.py
@@ -231,13 +231,12 @@ def _experiments_to_circuits(qobj):
         pulse_lib = qobj.config.pulse_library if hasattr(qobj.config, "pulse_library") else []
         # The dict update method did not work here; could investigate in the future
         if hasattr(qobj.config, "calibrations"):
-            circuit.calibrations = dict(
-                **circuit.calibrations, **_qobj_to_circuit_cals(qobj, pulse_lib)
-            )
+            circuit.calibrations = {
+                **circuit.calibrations,
+                **_qobj_to_circuit_cals(qobj, pulse_lib),
+            }
         if hasattr(exp.config, "calibrations"):
-            circuit.calibrations = dict(
-                **circuit.calibrations, **_qobj_to_circuit_cals(exp, pulse_lib)
-            )
+            circuit.calibrations = {**circuit.calibrations, **_qobj_to_circuit_cals(exp, pulse_lib)}
         circuits.append(circuit)
     return circuits
 
diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py
index c7d5885506fd..a6c5212e2330 100644
--- a/qiskit/compiler/assembler.py
+++ b/qiskit/compiler/assembler.py
@@ -350,20 +350,20 @@ def _parse_common_args(
     ]
 
     # create run configuration and populate
-    run_config_dict = dict(
-        shots=shots,
-        memory=memory,
-        seed_simulator=seed_simulator,
-        init_qubits=init_qubits,
-        rep_delay=rep_delay,
-        qubit_lo_freq=qubit_lo_freq,
-        meas_lo_freq=meas_lo_freq,
-        qubit_lo_range=qubit_lo_range,
-        meas_lo_range=meas_lo_range,
-        schedule_los=schedule_los,
-        n_qubits=n_qubits,
+    run_config_dict = {
+        "shots": shots,
+        "memory": memory,
+        "seed_simulator": seed_simulator,
+        "init_qubits": init_qubits,
+        "rep_delay": rep_delay,
+        "qubit_lo_freq": qubit_lo_freq,
+        "meas_lo_freq": meas_lo_freq,
+        "qubit_lo_range": qubit_lo_range,
+        "meas_lo_range": meas_lo_range,
+        "schedule_los": schedule_los,
+        "n_qubits": n_qubits,
         **run_config,
-    )
+    }
 
     return qobj_id, qobj_header, run_config_dict
 
@@ -452,15 +452,15 @@ def _parse_pulse_args(
         parametric_pulses = getattr(backend_config, "parametric_pulses", [])
 
     # create run configuration and populate
-    run_config_dict = dict(
-        meas_level=meas_level,
-        meas_return=meas_return,
-        meas_map=meas_map,
-        memory_slot_size=memory_slot_size,
-        rep_time=rep_time,
-        parametric_pulses=parametric_pulses,
+    run_config_dict = {
+        "meas_level": meas_level,
+        "meas_return": meas_return,
+        "meas_map": meas_map,
+        "memory_slot_size": memory_slot_size,
+        "rep_time": rep_time,
+        "parametric_pulses": parametric_pulses,
         **run_config,
-    )
+    }
     run_config = RunConfig(**{k: v for k, v in run_config_dict.items() if v is not None})
 
     return run_config
@@ -478,7 +478,7 @@ def _parse_circuit_args(
     """
     parameter_binds = parameter_binds or []
     # create run configuration and populate
-    run_config_dict = dict(parameter_binds=parameter_binds, **run_config)
+    run_config_dict = {"parameter_binds": parameter_binds, **run_config}
     if parametric_pulses is None:
         if backend:
             run_config_dict["parametric_pulses"] = getattr(
diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py
index 1bbecb6b65e8..7d73ab2ebcf6 100644
--- a/test/python/result/test_result.py
+++ b/test/python/result/test_result.py
@@ -56,7 +56,7 @@ def test_counts_no_header(self):
         no_header_processed_counts = {
             bin(int(bs[2:], 16))[2:]: counts for (bs, counts) in raw_counts.items()
         }
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data)
         result = Result(results=[exp_result], **self.base_result_args)
 
@@ -66,7 +66,7 @@ def test_counts_header(self):
         """Test that counts are extracted properly with header."""
         raw_counts = {"0x0": 4, "0x2": 10}
         processed_counts = {"0 0 00": 4, "0 0 10": 10}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(
             creg_sizes=[["c0", 2], ["c0", 1], ["c1", 1]], memory_slots=4
         )
@@ -81,7 +81,7 @@ def test_counts_by_name(self):
         """Test that counts are extracted properly by name."""
         raw_counts = {"0x0": 4, "0x2": 10}
         processed_counts = {"0 0 00": 4, "0 0 10": 10}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(
             creg_sizes=[["c0", 2], ["c0", 1], ["c1", 1]], memory_slots=4, name="a_name"
         )
@@ -107,7 +107,7 @@ def test_counts_duplicate_name(self):
     def test_result_repr(self):
         """Test that repr is contstructed correctly for a results object."""
         raw_counts = {"0x0": 4, "0x2": 10}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(
             creg_sizes=[["c0", 2], ["c0", 1], ["c1", 1]], memory_slots=4
         )
@@ -136,7 +136,7 @@ def test_multiple_circuits_counts(self):
         """
         raw_counts_1 = {"0x0": 5, "0x3": 12, "0x5": 9, "0xD": 6, "0xE": 2}
         processed_counts_1 = {"0000": 5, "0011": 12, "0101": 9, "1101": 6, "1110": 2}
-        data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1))
+        data_1 = models.ExperimentResultData(counts=raw_counts_1)
         exp_result_header_1 = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result_1 = models.ExperimentResult(
             shots=14, success=True, meas_level=2, data=data_1, header=exp_result_header_1
@@ -144,7 +144,7 @@ def test_multiple_circuits_counts(self):
 
         raw_counts_2 = {"0x1": 0, "0x4": 3, "0x6": 6, "0xA": 1, "0xB": 2}
         processed_counts_2 = {"0001": 0, "0100": 3, "0110": 6, "1010": 1, "1011": 2}
-        data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2))
+        data_2 = models.ExperimentResultData(counts=raw_counts_2)
         exp_result_header_2 = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result_2 = models.ExperimentResult(
             shots=14, success=True, meas_level=2, data=data_2, header=exp_result_header_2
@@ -152,7 +152,7 @@ def test_multiple_circuits_counts(self):
 
         raw_counts_3 = {"0xC": 27, "0xF": 20}
         processed_counts_3 = {"1100": 27, "1111": 20}
-        data_3 = models.ExperimentResultData(counts=dict(**raw_counts_3))
+        data_3 = models.ExperimentResultData(counts=raw_counts_3)
         exp_result_header_3 = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result_3 = models.ExperimentResult(
             shots=14, success=True, meas_level=2, data=data_3, header=exp_result_header_3
@@ -171,7 +171,7 @@ def test_multiple_circuits_counts(self):
     def test_marginal_counts(self):
         """Test that counts are marginalized correctly."""
         raw_counts = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0xE": 8}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result = models.ExperimentResult(
             shots=54, success=True, data=data, header=exp_result_header
@@ -322,7 +322,7 @@ def test_marginal_counts_result_inplace(self):
     def test_marginal_counts_result_creg_sizes(self):
         """Test that marginal_counts with Result input properly changes creg_sizes."""
         raw_counts = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0xE": 8}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(creg_sizes=[["c0", 1], ["c1", 3]], memory_slots=4)
         exp_result = models.ExperimentResult(
             shots=54, success=True, data=data, header=exp_result_header
@@ -343,7 +343,7 @@ def test_marginal_counts_result_creg_sizes(self):
     def test_marginal_counts_result_format(self):
         """Test that marginal_counts with format_marginal true properly formats output."""
         raw_counts_1 = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0x12": 8}
-        data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1))
+        data_1 = models.ExperimentResultData(counts=raw_counts_1)
         exp_result_header_1 = QobjExperimentHeader(
             creg_sizes=[["c0", 2], ["c1", 3]], memory_slots=5
         )
@@ -368,14 +368,14 @@ def test_marginal_counts_result_format(self):
     def test_marginal_counts_inplace_true(self):
         """Test marginal_counts(Result, inplace = True)"""
         raw_counts_1 = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0xE": 8}
-        data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1))
+        data_1 = models.ExperimentResultData(counts=raw_counts_1)
         exp_result_header_1 = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result_1 = models.ExperimentResult(
             shots=54, success=True, data=data_1, header=exp_result_header_1
         )
 
         raw_counts_2 = {"0x2": 5, "0x3": 8}
-        data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2))
+        data_2 = models.ExperimentResultData(counts=raw_counts_2)
         exp_result_header_2 = QobjExperimentHeader(creg_sizes=[["c0", 2]], memory_slots=2)
         exp_result_2 = models.ExperimentResult(
             shots=13, success=True, data=data_2, header=exp_result_header_2
@@ -393,14 +393,14 @@ def test_marginal_counts_inplace_true(self):
     def test_marginal_counts_inplace_false(self):
         """Test marginal_counts(Result, inplace=False)"""
         raw_counts_1 = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0xE": 8}
-        data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1))
+        data_1 = models.ExperimentResultData(counts=raw_counts_1)
         exp_result_header_1 = QobjExperimentHeader(creg_sizes=[["c0", 4]], memory_slots=4)
         exp_result_1 = models.ExperimentResult(
             shots=54, success=True, data=data_1, header=exp_result_header_1
         )
 
         raw_counts_2 = {"0x2": 5, "0x3": 8}
-        data_2 = models.ExperimentResultData(counts=dict(**raw_counts_2))
+        data_2 = models.ExperimentResultData(counts=raw_counts_2)
         exp_result_header_2 = QobjExperimentHeader(creg_sizes=[["c0", 2]], memory_slots=2)
         exp_result_2 = models.ExperimentResult(
             shots=13, success=True, data=data_2, header=exp_result_header_2
@@ -689,7 +689,7 @@ def setUp(self):
     def test_counts_int_out(self):
         """Test that fails when get_count is called with a nonexistent int."""
         raw_counts = {"0x0": 4, "0x2": 10}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data)
         result = Result(results=[exp_result], **self.base_result_args)
 
@@ -702,7 +702,7 @@ def test_counts_int_out(self):
     def test_counts_name_out(self):
         """Test that fails when get_count is called with a nonexistent name."""
         raw_counts = {"0x0": 4, "0x2": 10}
-        data = models.ExperimentResultData(counts=dict(**raw_counts))
+        data = models.ExperimentResultData(counts=raw_counts)
         exp_result_header = QobjExperimentHeader(
             creg_sizes=[["c0", 2], ["c0", 1], ["c1", 1]], memory_slots=4, name="a_name"
         )
@@ -735,7 +735,7 @@ def test_memory_int_out(self):
     def test_marginal_counts_no_cregs(self):
         """Test that marginal_counts without cregs See qiskit-terra/6430."""
         raw_counts_1 = {"0x0": 4, "0x1": 7, "0x2": 10, "0x6": 5, "0x9": 11, "0xD": 9, "0x12": 8}
-        data_1 = models.ExperimentResultData(counts=dict(**raw_counts_1))
+        data_1 = models.ExperimentResultData(counts=raw_counts_1)
         exp_result_header_1 = QobjExperimentHeader(memory_slots=5)
         exp_result_1 = models.ExperimentResult(
             shots=54, success=True, data=data_1, header=exp_result_header_1

From 180273216cf368c97226011d5c54f3d4734e42fd Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Fri, 26 Apr 2024 06:40:13 -0400
Subject: [PATCH 030/179] Add QPY format version table to the docs (#12284)

* Add QPY format version table to the docs

This commit adds a new table to the QPY module docs that outlines all
the supported QPY format versions for every qiskit/qiskit-terra release
that had qpy. This information is potentially useful when trying to
navigate generating QPY files across versions.

Fixes #12283

* Update qiskit/qpy/__init__.py
---
 qiskit/qpy/__init__.py | 129 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py
index 1274ff110be6..fed09b8717a6 100644
--- a/qiskit/qpy/__init__.py
+++ b/qiskit/qpy/__init__.py
@@ -111,6 +111,135 @@
 
 .. autoexception:: QPYLoadingDeprecatedFeatureWarning
 
+QPY format version history
+--------------------------
+
+If you're planning to load a QPY file between different Qiskit versions knowing
+which versions were available in a given release are useful. As the QPY is
+backwards compatible but not forwards compatible you need to ensure a given
+QPY format version was released in the release you're calling :func:`.load`
+with. The following table lists the QPY versions that were supported in every
+Qiskit (and qiskit-terra prior to Qiskit 1.0.0) release going back to the introduction
+of QPY in qiskit-terra 0.18.0.
+
+.. list-table: QPY Format Version History
+   :header-rows: 1
+
+   * - Qiskit (qiskit-terra for < 1.0.0) version
+     - :func:`.dump` format(s) output versions
+     - :func:`.load` maximum supported version (older format versions can always be read)
+   * - 1.1.0
+     - 10, 11, 12
+     - 12
+   * - 1.0.2
+     - 10, 11
+     - 12
+   * - 1.0.1
+     - 10, 11
+     - 11
+   * - 1.0.0
+     - 10, 11
+     - 11
+   * - 0.46.1
+     - 10
+     - 10
+   * - 0.45.3
+     - 10
+     - 10
+   * - 0.45.2
+     - 10
+     - 10
+   * - 0.45.1
+     - 10
+     - 10
+   * - 0.45.0
+     - 10
+     - 10
+   * - 0.25.3
+     - 9
+     - 9
+   * - 0.25.2
+     - 9
+     - 9
+   * - 0.25.1
+     - 9
+     - 9
+   * - 0.24.2
+     - 8
+     - 8
+   * - 0.24.1
+     - 7
+     - 7
+   * - 0.24.0
+     - 7
+     - 7
+   * - 0.23.3
+     - 6
+     - 6
+   * - 0.23.2
+     - 6
+     - 6
+   * - 0.23.1
+     - 6
+     - 6
+   * - 0.23.0
+     - 6
+     - 6
+   * - 0.22.4
+     - 5
+     - 5
+   * - 0.22.3
+     - 5
+     - 5
+   * - 0.22.2
+     - 5
+     - 5
+   * - 0.22.1
+     - 5
+     - 5
+   * - 0.22.0
+     - 5
+     - 5
+   * - 0.21.2
+     - 5
+     - 5
+   * - 0.21.1
+     - 5
+     - 5
+   * - 0.21.0
+     - 5
+     - 5
+   * - 0.20.2
+     - 4
+     - 4
+   * - 0.20.1
+     - 4
+     - 4
+   * - 0.20.0
+     - 4
+     - 4
+   * - 0.19.2
+     - 4
+     - 4
+   * - 0.19.1
+     - 3
+     - 3
+   * - 0.19.0
+     - 2
+     - 2
+   * - 0.18.3
+     - 1
+     - 1
+   * - 0.18.2
+     - 1
+     - 1
+   * - 0.18.1
+     - 1
+     - 1
+   * - 0.18.0
+     - 1
+     - 1
+
 .. _qpy_format:
 
 **********

From d084aebfe043d9a8ef20923ab806017ca36937d5 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Fri, 26 Apr 2024 16:30:07 +0100
Subject: [PATCH 031/179] Add Rust-based `SparsePauliOp.to_matrix` and Miri
 tests (#11388)

* Add Rust-based `SparsePauliOp.to_matrix`

This rewrites the numerical version of `SparsePauliOp.to_matrix` to be
written in parallelised Rust, building up the matrices row-by-row rather
than converting each contained operator to a matrix individually and
summing them.

The new algorithms are complete row-based, which is embarrassingly
parallel for dense matrices, and parallelisable with additional copies
and cumulative sums in the CSR case.

The two new algorithms are an asymptotic complexity improvement for both
dense and sparse matrices over the "sum the individual matrices"
version.  In the dense case, the cost goes from

        O(4 ** num_qubits * num_ops)

to

        O(4 ** num_qubits + (2 ** num_qubits) * reduced_num_ops)

where the first term is from the zeroing of the matrix, and the second
is from updating the elements in-place.  `reduced_num_ops` is the number
of explicitly stored operations after Pauli-operator uniqueness
compaction, so is upper-bounded as `4 ** num_qubits`.  (The Pauli
compaction goes as `O(num_ops)`, so is irrelevant to the complexity
discussion.) The CSR form of the algorithm goes as

        O(2 ** num_qubits * reduced_num_ops * lg(reduced_num_ops))

which (I think! - I didn't fully calculate it) is asymptotically the
same as before, but in practice the constant factors and intermediate
memory use are *dramatically* reduced, and the new algorithm is
threadable with an additional `O(2 ** num_qubits * reduced_num_ops)`
intermediate memory overhead (the serial form has only
`O(reduced_num_ops)` memory overhead).

The `object`-coefficients form is left as-is to avoid exploding the
complexity in Rust space; these objects are already slow and unsuited
for high-performance code, so the effects should be minimal.

* Add non-blocking Miri to CI

As we begin to include more `unsafe` code in the Rust-accelerated
components, it is becoming more important for us to test these in an
undefined-behaviour sanitiser.  This is done in a separate CI job
because:

- we do not yet know how stable Miri will be, so we don't want to block
  on it.

- some dependencies need their version-resolution patching to
  Miri-compatible versions, but we want to run our regular test suites
  with the same versions of packages we will be building against.

* Parallelise cumulative nnz sum

This parallelises the previously serial-only cumulative sum of the
`indptr` array of number of non-zero entries at the end.  In practice, I
didn't actually see any change in performance from this, but
philosophically it feels like the right thing to do.

* Update Miri pin to later version of crossbeam-epohc

* Improve error handling and messages

* Simplify unnecessary match

* Add link to environment variable configuration

* Add link to Rayon plumbing README

* Add explicit test of serial and parallel modes
---
 .github/workflows/miri.yml                    |  46 ++
 CONTRIBUTING.md                               |  52 ++
 crates/accelerate/src/lib.rs                  |   4 +
 crates/accelerate/src/rayon_ext.rs            | 171 +++++
 crates/accelerate/src/sparse_pauli_op.rs      | 617 +++++++++++++++++-
 crates/accelerate/src/test.rs                 |  24 +
 .../operators/symplectic/sparse_pauli_op.py   |  43 +-
 .../notes/spo-to-matrix-26445a791e24f62a.yaml |   8 +
 .../symplectic/test_sparse_pauli_op.py        |  32 +
 9 files changed, 983 insertions(+), 14 deletions(-)
 create mode 100644 .github/workflows/miri.yml
 create mode 100644 crates/accelerate/src/rayon_ext.rs
 create mode 100644 crates/accelerate/src/test.rs
 create mode 100644 releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml

diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml
new file mode 100644
index 000000000000..bdceb20c3008
--- /dev/null
+++ b/.github/workflows/miri.yml
@@ -0,0 +1,46 @@
+name: Miri
+on:
+  push:
+  pull_request:
+concurrency:
+  group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }}
+  # Only cancel in PR mode.  In push mode, don't cancel so we don't see spurious test "failures",
+  # and we get coverage reports on Coveralls for every push.
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+  miri:
+    if: github.repository_owner == 'Qiskit'
+    name: Miri
+    runs-on: ubuntu-latest
+    env:
+      RUSTUP_TOOLCHAIN: nightly
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install Rust toolchain
+        uses: dtolnay/rust-toolchain@nightly
+        with:
+          components: miri
+
+      - name: Prepare Miri
+        run: |
+          set -e
+          # Some of our dependencies aren't Miri-safe with their current release versions.  These
+          # need overriding with known-good versions to run against until the Miri-safe versions are
+          # released and updated in our Cargo.lock.
+          cat >>Cargo.toml < bool {
     let parallel_context = env::var("QISKIT_IN_PARALLEL")
diff --git a/crates/accelerate/src/rayon_ext.rs b/crates/accelerate/src/rayon_ext.rs
new file mode 100644
index 000000000000..af914a86d414
--- /dev/null
+++ b/crates/accelerate/src/rayon_ext.rs
@@ -0,0 +1,171 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2023
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+//! Extension structs for use with Rayon parallelism.
+
+// See https://github.com/rayon-rs/rayon/blob/v1.10.0/src/iter/plumbing/README.md (or a newer
+// version) for more of an explanation of how Rayon's plumbing works.
+
+use rayon::iter::plumbing::*;
+use rayon::prelude::*;
+
+pub trait ParallelSliceMutExt: ParallelSliceMut {
+    /// Create a parallel iterator over mutable chunks of uneven lengths for this iterator.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the sums of the given lengths do not add up to the length of the slice.
+    #[track_caller]
+    fn par_uneven_chunks_mut<'len, 'data>(
+        &'data mut self,
+        chunk_lengths: &'len [usize],
+    ) -> ParUnevenChunksMut<'len, 'data, T> {
+        let mut_slice = self.as_parallel_slice_mut();
+        let chunk_sum = chunk_lengths.iter().sum::();
+        let slice_len = mut_slice.len();
+        if chunk_sum != slice_len {
+            panic!("given slices of total size {chunk_sum} for a chunk of length {slice_len}");
+        }
+        ParUnevenChunksMut {
+            chunk_lengths,
+            data: mut_slice,
+        }
+    }
+}
+
+impl ParallelSliceMutExt for S where S: ParallelSliceMut {}
+
+/// Very similar to Rayon's [rayon::slice::ChunksMut], except that the lengths of the individual
+/// chunks are arbitrary, provided they sum to the total length of the slice.
+#[derive(Debug)]
+pub struct ParUnevenChunksMut<'len, 'data, T> {
+    chunk_lengths: &'len [usize],
+    data: &'data mut [T],
+}
+
+impl<'len, 'data, T: Send + 'data> ParallelIterator for ParUnevenChunksMut<'len, 'data, T> {
+    type Item = &'data mut [T];
+
+    #[track_caller]
+    fn drive_unindexed>(self, consumer: C) -> C::Result {
+        bridge(self, consumer)
+    }
+}
+
+impl<'len, 'data, T: Send + 'data> IndexedParallelIterator for ParUnevenChunksMut<'len, 'data, T> {
+    #[track_caller]
+    fn drive>(self, consumer: C) -> C::Result {
+        bridge(self, consumer)
+    }
+
+    fn len(&self) -> usize {
+        self.chunk_lengths.len()
+    }
+
+    #[track_caller]
+    fn with_producer>(self, callback: CB) -> CB::Output {
+        callback.callback(UnevenChunksMutProducer {
+            chunk_lengths: self.chunk_lengths,
+            data: self.data,
+        })
+    }
+}
+
+struct UnevenChunksMutProducer<'len, 'data, T: Send> {
+    chunk_lengths: &'len [usize],
+    data: &'data mut [T],
+}
+
+impl<'len, 'data, T: Send + 'data> Producer for UnevenChunksMutProducer<'len, 'data, T> {
+    type Item = &'data mut [T];
+    type IntoIter = UnevenChunksMutIter<'len, 'data, T>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        Self::IntoIter::new(self.chunk_lengths, self.data)
+    }
+
+    #[track_caller]
+    fn split_at(self, index: usize) -> (Self, Self) {
+        // Technically quadratic for a full-depth split, but let's worry about that later if needed.
+        let data_mid = self.chunk_lengths[..index].iter().sum();
+        let (chunks_left, chunks_right) = self.chunk_lengths.split_at(index);
+        let (data_left, data_right) = self.data.split_at_mut(data_mid);
+        (
+            Self {
+                chunk_lengths: chunks_left,
+                data: data_left,
+            },
+            Self {
+                chunk_lengths: chunks_right,
+                data: data_right,
+            },
+        )
+    }
+}
+
+#[must_use = "iterators do nothing unless consumed"]
+struct UnevenChunksMutIter<'len, 'data, T> {
+    chunk_lengths: &'len [usize],
+    // The extra `Option` wrapper here is to satisfy the borrow checker while we're splitting the
+    // `data` reference.  We need to consume `self`'s reference during the split before replacing
+    // it, which means we need to temporarily set the `data` ref to some unowned value.
+    // `Option<&mut [T]>` means we can replace it temporarily with the null reference, ensuring the
+    // mutable aliasing rules are always upheld.
+    data: Option<&'data mut [T]>,
+}
+
+impl<'len, 'data, T> UnevenChunksMutIter<'len, 'data, T> {
+    fn new(chunk_lengths: &'len [usize], data: &'data mut [T]) -> Self {
+        Self {
+            chunk_lengths,
+            data: Some(data),
+        }
+    }
+}
+
+impl<'len, 'data, T> Iterator for UnevenChunksMutIter<'len, 'data, T> {
+    type Item = &'data mut [T];
+
+    #[track_caller]
+    fn next(&mut self) -> Option {
+        if self.chunk_lengths.is_empty() {
+            return None;
+        }
+        let (out_data, rem_data) = self
+            .data
+            .take()
+            .unwrap()
+            .split_at_mut(self.chunk_lengths[0]);
+        self.chunk_lengths = &self.chunk_lengths[1..];
+        self.data = Some(rem_data);
+        Some(out_data)
+    }
+
+    fn size_hint(&self) -> (usize, Option) {
+        (self.chunk_lengths.len(), Some(self.chunk_lengths.len()))
+    }
+}
+impl<'len, 'data, T> ExactSizeIterator for UnevenChunksMutIter<'len, 'data, T> {}
+impl<'len, 'data, T> DoubleEndedIterator for UnevenChunksMutIter<'len, 'data, T> {
+    #[track_caller]
+    fn next_back(&mut self) -> Option {
+        if self.chunk_lengths.is_empty() {
+            return None;
+        }
+        let pos = self.chunk_lengths.len() - 1;
+        let data_pos = self.data.as_ref().map(|x| x.len()).unwrap() - self.chunk_lengths[pos];
+        let (rem_data, out_data) = self.data.take().unwrap().split_at_mut(data_pos);
+        self.chunk_lengths = &self.chunk_lengths[..pos];
+        self.data = Some(rem_data);
+        Some(out_data)
+    }
+}
diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs
index c8e0f0fb9316..5d6a82df7940 100644
--- a/crates/accelerate/src/sparse_pauli_op.rs
+++ b/crates/accelerate/src/sparse_pauli_op.rs
@@ -10,16 +10,22 @@
 // copyright notice, and modified files need to carry a notice indicating
 // that they have been altered from the originals.
 
-use pyo3::exceptions::PyValueError;
+use pyo3::exceptions::{PyRuntimeError, PyValueError};
 use pyo3::prelude::*;
+use pyo3::types::PyTuple;
 use pyo3::wrap_pyfunction;
 use pyo3::Python;
 
+use numpy::prelude::*;
+use numpy::{PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2, PyUntypedArrayMethods};
+
 use hashbrown::HashMap;
 use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis};
 use num_complex::Complex64;
 use num_traits::Zero;
-use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray2, PyUntypedArrayMethods};
+use rayon::prelude::*;
+
+use crate::rayon_ext::*;
 
 /// Find the unique elements of an array.
 ///
@@ -71,9 +77,49 @@ enum Pauli {
     Z,
 }
 
+/// Pack a 2D array of Booleans into a given width.  Returns an error if the input array is
+/// too large to be packed into u64.
+fn pack_bits(bool_arr: ArrayView2) -> Result, ()> {
+    let num_qubits = bool_arr.shape()[1];
+    if num_qubits > (u64::BITS as usize) {
+        return Err(());
+    }
+    let slack = num_qubits % 8;
+    let pack_row = |row: ArrayView1| -> u64 {
+        let mut val: u64 = 0;
+        let mut shift = 0;
+        for chunk in row.exact_chunks(8) {
+            val |= ((chunk[0] as u8
+                | ((chunk[1] as u8) << 1)
+                | ((chunk[2] as u8) << 2)
+                | ((chunk[3] as u8) << 3)
+                | ((chunk[4] as u8) << 4)
+                | ((chunk[5] as u8) << 5)
+                | ((chunk[6] as u8) << 6)
+                | ((chunk[7] as u8) << 7)) as u64)
+                << shift;
+            shift += 8;
+        }
+        if slack > 0 {
+            for (i, b) in row
+                .slice(s![num_qubits - slack..num_qubits])
+                .iter()
+                .enumerate()
+            {
+                val |= (*b as u64) << (shift + i);
+            }
+        }
+        val
+    };
+    Ok(bool_arr
+        .axis_iter(Axis(0))
+        .map(pack_row)
+        .collect::>())
+}
+
 /// A complete ZX-convention representation of a Pauli decomposition.  This is all the components
 /// necessary to construct a Qiskit-space :class:`.SparsePauliOp`, where :attr:`phases` is in the
-/// ZX convention.
+/// ZX convention.  This class is just meant for interoperation between Rust and Python.
 #[pyclass(module = "qiskit._accelerate.sparse_pauli_op")]
 pub struct ZXPaulis {
     #[pyo3(get)]
@@ -86,6 +132,196 @@ pub struct ZXPaulis {
     pub coeffs: Py>,
 }
 
+#[pymethods]
+impl ZXPaulis {
+    #[new]
+    fn __new__(
+        x: &Bound>,
+        z: &Bound>,
+        phases: &Bound>,
+        coeffs: &Bound>,
+    ) -> PyResult {
+        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 {:?}",
+                [num_ops, num_qubits],
+                z.shape()
+            )));
+        }
+        if phases.len() != num_ops || coeffs.len() != num_ops {
+            return Err(PyValueError::new_err(format!(
+                "mismatched dimensions: 'x' and 'z' have {} operator(s), 'phase' has {} and 'coeffs' has {}",
+                num_ops,
+                phases.len(),
+                coeffs.len(),
+            )));
+        }
+
+        Ok(Self {
+            x: x.to_owned().unbind(),
+            z: z.to_owned().unbind(),
+            phases: phases.to_owned().unbind(),
+            coeffs: coeffs.to_owned().unbind(),
+        })
+    }
+}
+
+impl ZXPaulis {
+    /// Attempt to acquire a Rust-enforced Rust-only immutable borrow onto the underlying
+    /// Python-space data. This returns `None` if any of the underlying arrays already has a
+    /// mutable borrow taken out onto it.
+    pub fn try_readonly<'a, 'py>(&'a self, py: Python<'py>) -> Option>
+    where
+        'a: 'py,
+    {
+        Some(ZXPaulisReadonly {
+            x: self.x.bind(py).try_readonly().ok()?,
+            z: self.z.bind(py).try_readonly().ok()?,
+            phases: self.phases.bind(py).try_readonly().ok()?,
+            coeffs: self.coeffs.bind(py).try_readonly().ok()?,
+        })
+    }
+}
+
+/// Intermediate structure that represents readonly views onto the Python-space sparse Pauli data.
+/// This is used in the chained methods so that the syntactical temporary lifetime extension can
+/// occur; we can't have the readonly array temporaries only live within a method that returns
+/// [ZXPaulisView], because otherwise the lifetimes of the [PyReadonlyArray] elements will be too
+/// short.
+pub struct ZXPaulisReadonly<'a> {
+    x: PyReadonlyArray2<'a, bool>,
+    z: PyReadonlyArray2<'a, bool>,
+    phases: PyReadonlyArray1<'a, u8>,
+    coeffs: PyReadonlyArray1<'a, Complex64>,
+}
+
+impl ZXPaulisReadonly<'_> {
+    /// Get a [ndarray] view of the data of these [rust-numpy] objects.
+    fn as_array(&self) -> ZXPaulisView {
+        ZXPaulisView {
+            x: self.x.as_array(),
+            z: self.z.as_array(),
+            phases: self.phases.as_array(),
+            coeffs: self.coeffs.as_array(),
+        }
+    }
+}
+
+/// Intermediate structure that represents [ndarray] views onto the Python-space sparse Pauli data
+/// in the ZX convention.  This can be used directly by Rust methods if desired, or bit-packed into
+/// a matrix-representation format [MatrixCompressedPaulis] using the [compress] method.
+pub struct ZXPaulisView<'py> {
+    x: ArrayView2<'py, bool>,
+    z: ArrayView2<'py, bool>,
+    phases: ArrayView1<'py, u8>,
+    coeffs: ArrayView1<'py, Complex64>,
+}
+
+impl<'py> ZXPaulisView<'py> {
+    /// The number of qubits this operator acts on.
+    pub fn num_qubits(&self) -> usize {
+        self.x.shape()[1]
+    }
+
+    /// Convert the ZX representation into a bitpacked internal representation.  See the
+    /// documentation of [MatrixCompressedPaulis] for details of the changes to the Pauli
+    /// convention and representation.
+    pub fn matrix_compress(&self) -> PyResult {
+        let num_qubits = self.num_qubits();
+        // This is obviously way too big for a dense operator, and SciPy limits us to using `i64`
+        // for the index and indptr types, so (except for some synthetic cases), it's not possible
+        // for us to work with a larger matrix than this.
+        if num_qubits > 63 {
+            return Err(PyValueError::new_err(format!(
+                "{num_qubits} is too many qubits to convert to a matrix"
+            )));
+        }
+        if num_qubits == 0 {
+            return Ok(MatrixCompressedPaulis {
+                num_qubits: 0,
+                x_like: Vec::new(),
+                z_like: Vec::new(),
+                coeffs: self.coeffs.to_vec(),
+            });
+        }
+        let x_like = pack_bits(self.x).expect("x should already be validated as <64 qubits");
+        let z_like = pack_bits(self.z).expect("z should already be validated as <64 qubits");
+        let coeffs = x_like
+            .iter()
+            .zip(z_like.iter())
+            .zip(self.phases.iter().zip(self.coeffs.iter()))
+            .map(|((xs, zs), (&phase, &coeff))| {
+                let ys = (xs & zs).count_ones();
+                match (phase as u32 + ys) % 4 {
+                    0 => coeff,
+                    1 => Complex64::new(coeff.im, -coeff.re),
+                    2 => Complex64::new(-coeff.re, -coeff.im),
+                    3 => Complex64::new(-coeff.im, coeff.re),
+                    _ => unreachable!(),
+                }
+            })
+            .collect::>();
+        Ok(MatrixCompressedPaulis {
+            num_qubits: num_qubits as u8,
+            x_like,
+            z_like,
+            coeffs,
+        })
+    }
+}
+
+/// Temporary bit-compressed storage of the Pauli string.  The [coeffs] are reinterpreted to
+/// include the old `phase` component in them directly, plus the factors of `-i` stemming from `Y`
+/// components.  The result is that the [coeffs] now more directly represent entries in a matrix,
+/// while [x_like] and [z_like] are no longer direct measures of `X` and `Z` elements (as in the ZX
+/// convention), but are instead only a marker of the column and parity respectively.
+///
+/// In other words, `row_num ^ x_like` gives the column number of an element, while
+/// `(row_num & z_like).count_ones()` counts multiplicative factors of `-1` to be applied to
+/// `coeff` when placing it at `(row_num, col_num)` in an output matrix.
+pub struct MatrixCompressedPaulis {
+    num_qubits: u8,
+    x_like: Vec,
+    z_like: Vec,
+    coeffs: Vec,
+}
+
+impl MatrixCompressedPaulis {
+    /// The number of qubits this operator acts on.
+    pub fn num_qubits(&self) -> usize {
+        self.num_qubits as usize
+    }
+
+    /// The number of explicitly stored operators in the sum.
+    pub fn num_ops(&self) -> usize {
+        self.coeffs.len()
+    }
+
+    /// Sum coefficients that correspond to the same Pauli operator; this reduces the number of
+    /// explicitly stored operations, if there are duplicates.  After the summation, any terms that
+    /// have become zero are dropped.
+    pub fn combine(&mut self) {
+        let mut hash_table = HashMap::<(u64, u64), Complex64>::with_capacity(self.coeffs.len());
+        for (key, coeff) in self
+            .x_like
+            .drain(..)
+            .zip(self.z_like.drain(..))
+            .zip(self.coeffs.drain(..))
+        {
+            *hash_table.entry(key).or_insert(Complex64::new(0.0, 0.0)) += coeff;
+        }
+        for ((x, z), coeff) in hash_table {
+            if coeff == Complex64::new(0.0, 0.0) {
+                continue;
+            }
+            self.x_like.push(x);
+            self.z_like.push(z);
+            self.coeffs.push(coeff);
+        }
+    }
+}
+
 /// Decompose a dense complex operator into the symplectic Pauli representation in the
 /// ZX-convention.
 ///
@@ -257,10 +493,385 @@ fn decompose_dense_inner(
     );
 }
 
+/// Convert the given [ZXPaulis] object to a dense 2D Numpy matrix.
+#[pyfunction]
+#[pyo3(signature = (/, paulis, force_serial=false))]
+pub fn to_matrix_dense<'py>(
+    py: Python<'py>,
+    paulis: &ZXPaulis,
+    force_serial: bool,
+) -> PyResult>> {
+    let paulis_readonly = paulis
+        .try_readonly(py)
+        .ok_or_else(|| PyRuntimeError::new_err("could not produce a safe view onto the data"))?;
+    let mut paulis = paulis_readonly.as_array().matrix_compress()?;
+    paulis.combine();
+    let side = 1usize << paulis.num_qubits();
+    let parallel = !force_serial && crate::getenv_use_multiple_threads();
+    let out = to_matrix_dense_inner(&paulis, parallel);
+    PyArray1::from_vec_bound(py, out).reshape([side, side])
+}
+
+/// Inner worker of the Python-exposed [to_matrix_dense].  This is separate primarily to allow
+/// Rust-space unit testing even if Python isn't available for execution.  This returns a C-ordered
+/// [Vec] of the 2D matrix.
+fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec {
+    let side = 1usize << paulis.num_qubits();
+    #[allow(clippy::uninit_vec)]
+    let mut out = {
+        let mut out = Vec::with_capacity(side * side);
+        // SAFETY: we iterate through the vec in chunks of `side`, and start each row by filling it
+        // with zeros before ever reading from it.  It's fine to overwrite the uninitialised memory
+        // because `Complex64: !Drop`.
+        unsafe { out.set_len(side * side) };
+        out
+    };
+    let write_row = |(i_row, row): (usize, &mut [Complex64])| {
+        // Doing the initialisation here means that when we're in parallel contexts, we do the
+        // zeroing across the whole threadpool.  This also seems to give a speed-up in serial
+        // contexts, but I don't understand that. ---Jake
+        row.fill(Complex64::new(0.0, 0.0));
+        for ((&x_like, &z_like), &coeff) in paulis
+            .x_like
+            .iter()
+            .zip(paulis.z_like.iter())
+            .zip(paulis.coeffs.iter())
+        {
+            // Technically this discards part of the storable data, but in practice, a dense
+            // operator with more than 32 qubits needs in the region of 1 ZiB memory.  We still use
+            // `u64` to help sparse-matrix construction, though.
+            let coeff = if (i_row as u32 & z_like as u32).count_ones() % 2 == 0 {
+                coeff
+            } else {
+                -coeff
+            };
+            row[i_row ^ (x_like as usize)] += coeff;
+        }
+    };
+    if parallel {
+        out.par_chunks_mut(side).enumerate().for_each(write_row);
+    } else {
+        out.chunks_mut(side).enumerate().for_each(write_row);
+    }
+    out
+}
+
+type CSRData = (Vec, Vec, Vec);
+type ToCSRData = fn(&MatrixCompressedPaulis) -> CSRData;
+
+/// Convert the given [ZXPaulis] object to the three-array CSR form.  The output type of the
+/// `indices` and `indptr` matrices will be `i32` if that type is guaranteed to be able to hold the
+/// number of non-zeros, otherwise it will be `i64`.  Signed types are used to match Scipy.  `i32`
+/// is preferentially returned, because Scipy will downcast to this on `csr_matrix` construction if
+/// all array elements would fit.  For large operators with significant cancellation, it is
+/// possible that `i64` will be returned when `i32` would suffice, but this will not cause
+/// unsoundness, just a copy overhead when constructing the Scipy matrix.
+#[pyfunction]
+#[pyo3(signature = (/, paulis, force_serial=false))]
+pub fn to_matrix_sparse(
+    py: Python,
+    paulis: &ZXPaulis,
+    force_serial: bool,
+) -> PyResult> {
+    let paulis_readonly = paulis
+        .try_readonly(py)
+        .ok_or_else(|| PyRuntimeError::new_err("could not produce a safe view onto the data"))?;
+    let mut paulis = paulis_readonly.as_array().matrix_compress()?;
+    paulis.combine();
+
+    // This deliberately erases the Rust types in the output so we can return either 32- or 64-bit
+    // indices as appropriate without breaking Rust's typing.
+    fn to_py_tuple(py: Python, csr_data: CSRData) -> Py
+    where
+        T: numpy::Element,
+    {
+        let (values, indices, indptr) = csr_data;
+        (
+            PyArray1::from_vec_bound(py, values),
+            PyArray1::from_vec_bound(py, indices),
+            PyArray1::from_vec_bound(py, indptr),
+        )
+            .into_py(py)
+    }
+
+    // Pessimistic estimation of whether we can fit in `i32`.  If there's any risk of overflowing
+    // `i32`, we use `i64`, but Scipy will always try to downcast to `i32`, so we try to match it.
+    let max_entries_per_row = (paulis.num_ops() as u64).min(1u64 << (paulis.num_qubits() - 1));
+    let use_32_bit =
+        max_entries_per_row.saturating_mul(1u64 << paulis.num_qubits()) <= (i32::MAX as u64);
+    if use_32_bit {
+        let to_sparse: ToCSRData = if crate::getenv_use_multiple_threads() && !force_serial {
+            to_matrix_sparse_parallel_32
+        } else {
+            to_matrix_sparse_serial_32
+        };
+        Ok(to_py_tuple(py, to_sparse(&paulis)))
+    } else {
+        let to_sparse: ToCSRData = if crate::getenv_use_multiple_threads() && !force_serial {
+            to_matrix_sparse_parallel_64
+        } else {
+            to_matrix_sparse_serial_64
+        };
+        Ok(to_py_tuple(py, to_sparse(&paulis)))
+    }
+}
+
+/// Copy several slices into a single flat vec, in parallel.  Allocates a temporary `Vec` of
+/// the same length as the input slice to track the chunking.
+fn copy_flat_parallel(slices: &[U]) -> Vec
+where
+    T: Copy + Send + Sync,
+    U: AsRef<[T]> + Sync,
+{
+    let lens = slices
+        .iter()
+        .map(|slice| slice.as_ref().len())
+        .collect::>();
+    let size = lens.iter().sum();
+    #[allow(clippy::uninit_vec)]
+    let mut out = {
+        let mut out = Vec::with_capacity(size);
+        // SAFETY: we've just calculated that the lengths of the given vecs add up to the right
+        // thing, and we're about to copy in the data from each of them into this uninitialised
+        // array.  It's guaranteed safe to write `T` to the uninitialised space, because `Copy`
+        // implies `!Drop`.
+        unsafe { out.set_len(size) };
+        out
+    };
+    out.par_uneven_chunks_mut(&lens)
+        .zip(slices.par_iter().map(|x| x.as_ref()))
+        .for_each(|(out_slice, in_slice)| out_slice.copy_from_slice(in_slice));
+    out
+}
+
+macro_rules! impl_to_matrix_sparse {
+    ($serial_fn:ident, $parallel_fn:ident, $int_ty:ty, $uint_ty:ty $(,)?) => {
+        /// Build CSR data arrays for the matrix-compressed set of the Pauli operators, using a
+        /// completely serial strategy.
+        fn $serial_fn(paulis: &MatrixCompressedPaulis) -> CSRData<$int_ty> {
+            let side = 1 << paulis.num_qubits();
+            let num_ops = paulis.num_ops();
+            if num_ops == 0 {
+                return (vec![], vec![], vec![0; side + 1]);
+            }
+
+            let mut order = (0..num_ops).collect::>();
+            let mut values = Vec::::with_capacity(side * (num_ops + 1) / 2);
+            let mut indices = Vec::<$int_ty>::with_capacity(side * (num_ops + 1) / 2);
+            let mut indptr: Vec<$int_ty> = vec![0; side + 1];
+            let mut nnz = 0;
+            for i_row in 0..side {
+                order.sort_unstable_by(|&a, &b| {
+                    ((i_row as $uint_ty) ^ (paulis.x_like[a] as $uint_ty))
+                        .cmp(&((i_row as $uint_ty) ^ (paulis.x_like[b] as $uint_ty)))
+                });
+                let mut running = Complex64::new(0.0, 0.0);
+                let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize);
+                for (x_like, z_like, coeff) in order
+                    .iter()
+                    .map(|&i| (paulis.x_like[i], paulis.z_like[i], paulis.coeffs[i]))
+                {
+                    let coeff =
+                        if ((i_row as $uint_ty) & (z_like as $uint_ty)).count_ones() % 2 == 0 {
+                            coeff
+                        } else {
+                            -coeff
+                        };
+                    let index = i_row ^ (x_like as usize);
+                    if index == prev_index {
+                        running += coeff;
+                    } else {
+                        nnz += 1;
+                        values.push(running);
+                        indices.push(prev_index as $int_ty);
+                        running = coeff;
+                        prev_index = index;
+                    }
+                }
+                nnz += 1;
+                values.push(running);
+                indices.push(prev_index as $int_ty);
+                indptr[i_row + 1] = nnz;
+            }
+            (values, indices, indptr)
+        }
+
+        /// Build CSR data arrays for the matrix-compressed set of the Pauli operators, using a
+        /// parallel strategy.  This involves more data copying than the serial form, so there is a
+        /// nontrivial amount of parallel overhead.
+        fn $parallel_fn(paulis: &MatrixCompressedPaulis) -> CSRData<$int_ty> {
+            let side = 1 << paulis.num_qubits();
+            let num_ops = paulis.num_ops();
+            if num_ops == 0 {
+                return (vec![], vec![], vec![0; side + 1]);
+            }
+
+            let mut indptr = Vec::<$int_ty>::with_capacity(side + 1);
+            indptr.push(0);
+            // SAFETY: we allocate the space for the `indptr` array here, then each thread writes
+            // in the number of nonzero entries for each row it was responsible for.  We know ahead
+            // of time exactly how many entries we need (one per row, plus an explicit 0 to start).
+            // It's also important that `$int_ty` does not implement `Drop`, since otherwise it
+            // will be called on uninitialised memory (all primitive int types satisfy this).
+            unsafe {
+                indptr.set_len(side + 1);
+            }
+
+            // The parallel overhead from splitting a subtask is fairly high (allocating and
+            // potentially growing a couple of vecs), so we're trading off some of Rayon's ability
+            // to keep threads busy by subdivision with minimising overhead; we're setting the
+            // chunk size such that the iterator will have as many elements as there are threads.
+            let num_threads = rayon::current_num_threads();
+            let chunk_size = (side + num_threads - 1) / num_threads;
+            let mut values_chunks = Vec::with_capacity(num_threads);
+            let mut indices_chunks = Vec::with_capacity(num_threads);
+            // SAFETY: the slice here is uninitialised data; it must not be read.
+            indptr[1..]
+                .par_chunks_mut(chunk_size)
+                .enumerate()
+                .map(|(i, indptr_chunk)| {
+                    let start = chunk_size * i;
+                    let end = (chunk_size * (i + 1)).min(side);
+                    let mut order = (0..num_ops).collect::>();
+                    // Since we compressed the Paulis by summing equal elements, we're
+                    // lower-bounded on the number of elements per row by this value, up to
+                    // cancellations.  This should be a reasonable trade-off between sometimes
+                    // expandin the vector and overallocation.
+                    let mut values =
+                        Vec::::with_capacity(chunk_size * (num_ops + 1) / 2);
+                    let mut indices = Vec::<$int_ty>::with_capacity(chunk_size * (num_ops + 1) / 2);
+                    let mut nnz = 0;
+                    for i_row in start..end {
+                        order.sort_unstable_by(|&a, &b| {
+                            (i_row as $uint_ty ^ paulis.x_like[a] as $uint_ty)
+                                .cmp(&(i_row as $uint_ty ^ paulis.x_like[b] as $uint_ty))
+                        });
+                        let mut running = Complex64::new(0.0, 0.0);
+                        let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize);
+                        for (x_like, z_like, coeff) in order
+                            .iter()
+                            .map(|&i| (paulis.x_like[i], paulis.z_like[i], paulis.coeffs[i]))
+                        {
+                            let coeff =
+                                if (i_row as $uint_ty & z_like as $uint_ty).count_ones() % 2 == 0 {
+                                    coeff
+                                } else {
+                                    -coeff
+                                };
+                            let index = i_row ^ (x_like as usize);
+                            if index == prev_index {
+                                running += coeff;
+                            } else {
+                                nnz += 1;
+                                values.push(running);
+                                indices.push(prev_index as $int_ty);
+                                running = coeff;
+                                prev_index = index;
+                            }
+                        }
+                        nnz += 1;
+                        values.push(running);
+                        indices.push(prev_index as $int_ty);
+                        // When we write it, this is a cumulative `nnz` _within the chunk_.  We
+                        // turn that into a proper cumulative sum in serial afterwards.
+                        indptr_chunk[i_row - start] = nnz;
+                    }
+                    (values, indices)
+                })
+                .unzip_into_vecs(&mut values_chunks, &mut indices_chunks);
+            // Turn the chunkwise nnz counts into absolute nnz counts.
+            let mut start_nnz = 0usize;
+            let chunk_nnz = values_chunks
+                .iter()
+                .map(|chunk| {
+                    let prev = start_nnz;
+                    start_nnz += chunk.len();
+                    prev as $int_ty
+                })
+                .collect::>();
+            indptr[1..]
+                .par_chunks_mut(chunk_size)
+                .zip(chunk_nnz)
+                .for_each(|(indptr_chunk, start_nnz)| {
+                    indptr_chunk.iter_mut().for_each(|nnz| *nnz += start_nnz);
+                });
+            // Concatenate the chunkwise values and indices togther.
+            let values = copy_flat_parallel(&values_chunks);
+            let indices = copy_flat_parallel(&indices_chunks);
+            (values, indices, indptr)
+        }
+    };
+}
+
+impl_to_matrix_sparse!(
+    to_matrix_sparse_serial_32,
+    to_matrix_sparse_parallel_32,
+    i32,
+    u32
+);
+impl_to_matrix_sparse!(
+    to_matrix_sparse_serial_64,
+    to_matrix_sparse_parallel_64,
+    i64,
+    u64
+);
+
 #[pymodule]
 pub fn sparse_pauli_op(m: &Bound) -> PyResult<()> {
     m.add_wrapped(wrap_pyfunction!(unordered_unique))?;
     m.add_wrapped(wrap_pyfunction!(decompose_dense))?;
+    m.add_wrapped(wrap_pyfunction!(to_matrix_dense))?;
+    m.add_wrapped(wrap_pyfunction!(to_matrix_sparse))?;
     m.add_class::()?;
     Ok(())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test::*;
+
+    // The purpose of these tests is more about exercising the `unsafe` code; we test for full
+    // correctness from Python space.
+
+    fn example_paulis() -> MatrixCompressedPaulis {
+        MatrixCompressedPaulis {
+            num_qubits: 4,
+            x_like: vec![0b0000, 0b0001, 0b0010, 0b1100, 0b1010, 0b0000],
+            z_like: vec![0b1000, 0b0110, 0b1001, 0b0100, 0b1010, 0b1000],
+            // Deliberately using multiples of small powers of two so the floating-point addition
+            // of them is associative.
+            coeffs: vec![
+                Complex64::new(0.25, 0.5),
+                Complex64::new(0.125, 0.25),
+                Complex64::new(0.375, 0.125),
+                Complex64::new(-0.375, 0.0625),
+                Complex64::new(-0.5, -0.25),
+            ],
+        }
+    }
+
+    #[test]
+    fn dense_threaded_and_serial_equal() {
+        let paulis = example_paulis();
+        let parallel = in_scoped_thread_pool(|| to_matrix_dense_inner(&paulis, true)).unwrap();
+        let serial = to_matrix_dense_inner(&paulis, false);
+        assert_eq!(parallel, serial);
+    }
+
+    #[test]
+    fn sparse_threaded_and_serial_equal_32() {
+        let paulis = example_paulis();
+        let parallel = in_scoped_thread_pool(|| to_matrix_sparse_parallel_32(&paulis)).unwrap();
+        let serial = to_matrix_sparse_serial_32(&paulis);
+        assert_eq!(parallel, serial);
+    }
+
+    #[test]
+    fn sparse_threaded_and_serial_equal_64() {
+        let paulis = example_paulis();
+        let parallel = in_scoped_thread_pool(|| to_matrix_sparse_parallel_64(&paulis)).unwrap();
+        let serial = to_matrix_sparse_serial_64(&paulis);
+        assert_eq!(parallel, serial);
+    }
+}
diff --git a/crates/accelerate/src/test.rs b/crates/accelerate/src/test.rs
new file mode 100644
index 000000000000..dac51499202b
--- /dev/null
+++ b/crates/accelerate/src/test.rs
@@ -0,0 +1,24 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2023
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+/// Helper for tests that involve calling Rayon code from within Miri.  This runs the given
+/// function in a scoped threadpool, which is then immediately dropped.  This means that Miri will
+/// not complain about the global (static) threads that are not joined when the process exits,
+/// which is deliberate.
+pub fn in_scoped_thread_pool(worker: F) -> Result
+where
+    T: Send,
+    F: FnOnce() -> T + Send,
+{
+    ::rayon::ThreadPoolBuilder::new()
+        .build_scoped(::rayon::ThreadBuilder::run, |pool| pool.install(worker))
+}
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index dc445509b0e3..dffe5b2396b2 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -23,7 +23,13 @@
 import numpy as np
 import rustworkx as rx
 
-from qiskit._accelerate.sparse_pauli_op import unordered_unique, decompose_dense
+from qiskit._accelerate.sparse_pauli_op import (
+    ZXPaulis,
+    decompose_dense,
+    to_matrix_dense,
+    to_matrix_sparse,
+    unordered_unique,
+)
 from qiskit.circuit.parameter import Parameter
 from qiskit.circuit.parameterexpression import ParameterExpression
 from qiskit.circuit.parametertable import ParameterView
@@ -925,24 +931,39 @@ def to_list(self, array: bool = False):
             return labels
         return labels.tolist()
 
-    def to_matrix(self, sparse: bool = False) -> np.ndarray:
+    def to_matrix(self, sparse: bool = False, force_serial: bool = False) -> np.ndarray:
         """Convert to a dense or sparse matrix.
 
         Args:
-            sparse (bool): if True return a sparse CSR matrix, otherwise
-                           return dense Numpy array (Default: False).
+            sparse: if ``True`` return a sparse CSR matrix, otherwise return dense Numpy
+                array (the default).
+            force_serial: if ``True``, use an unthreaded implementation, regardless of the state of
+                the `Qiskit threading-control environment variables
+                `__.
+                By default, this will use threaded parallelism over the available CPUs.
 
         Returns:
             array: A dense matrix if `sparse=False`.
             csr_matrix: A sparse matrix in CSR format if `sparse=True`.
         """
-        mat = None
-        for i in self.matrix_iter(sparse=sparse):
-            if mat is None:
-                mat = i
-            else:
-                mat += i
-        return mat
+        if self.coeffs.dtype == object:
+            # Fallback to slow Python-space method.
+            return sum(self.matrix_iter(sparse=sparse))
+
+        pauli_list = self.paulis
+        zx = ZXPaulis(
+            pauli_list.x.astype(np.bool_),
+            pauli_list.z.astype(np.bool_),
+            pauli_list.phase.astype(np.uint8),
+            self.coeffs.astype(np.complex128),
+        )
+        if sparse:
+            from scipy.sparse import csr_matrix
+
+            data, indices, indptr = to_matrix_sparse(zx, force_serial=force_serial)
+            side = 1 << self.num_qubits
+            return csr_matrix((data, indices, indptr), shape=(side, side))
+        return to_matrix_dense(zx, force_serial=force_serial)
 
     def to_operator(self) -> Operator:
         """Convert to a matrix Operator object"""
diff --git a/releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml b/releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml
new file mode 100644
index 000000000000..135e83ef99b1
--- /dev/null
+++ b/releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    The performance of :meth:`.SparsePauliOp.to_matrix` has been greatly improved for both dense and
+    sparse forms.  By default, both will now take advantage of threaded parallelism available on
+    your system, subject to the ``RAYON_NUM_THREADS`` environment variable.  You can temporarily
+    force serial execution using the new ``force_serial`` Boolean argument to
+    :meth:`~.SparsePauliOp.to_matrix`.
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 fdbfb4d4201d..330fd53bc35d 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
@@ -15,9 +15,11 @@
 import itertools as it
 import unittest
 import numpy as np
+import scipy.sparse
 import rustworkx as rx
 from ddt import ddt
 
+
 from qiskit import QiskitError
 from qiskit.circuit import ParameterExpression, Parameter, ParameterVector
 from qiskit.circuit.parametertable import ParameterView
@@ -259,6 +261,36 @@ def test_to_matrix_large(self):
         np.testing.assert_array_equal(spp_op.to_matrix(), target)
         np.testing.assert_array_equal(spp_op.to_matrix(sparse=True).toarray(), target)
 
+    def test_to_matrix_zero(self):
+        """Test `to_matrix` with a zero operator."""
+        num_qubits = 4
+        zero_numpy = np.zeros((2**num_qubits, 2**num_qubits), dtype=np.complex128)
+        zero = SparsePauliOp.from_list([], num_qubits=num_qubits)
+
+        zero_dense = zero.to_matrix(sparse=False)
+        np.testing.assert_array_equal(zero_dense, zero_numpy)
+
+        zero_sparse = zero.to_matrix(sparse=True)
+        self.assertIsInstance(zero_sparse, scipy.sparse.csr_matrix)
+        np.testing.assert_array_equal(zero_sparse.A, zero_numpy)
+
+    def test_to_matrix_parallel_vs_serial(self):
+        """Parallel execution should produce the same results as serial execution up to
+        floating-point associativity effects."""
+        # Using powers-of-two coefficients to make floating-point arithmetic associative so we can
+        # do bit-for-bit assertions.  Choose labels that have at least few overlapping locations.
+        labels = ["XZIXYX", "YIIYXY", "ZZZIIZ", "IIIIII"]
+        coeffs = [0.25, 0.125j, 0.5 - 0.25j, -0.125 + 0.5j]
+        op = SparsePauliOp(labels, coeffs)
+        np.testing.assert_array_equal(
+            op.to_matrix(sparse=True, force_serial=False).toarray(),
+            op.to_matrix(sparse=True, force_serial=True).toarray(),
+        )
+        np.testing.assert_array_equal(
+            op.to_matrix(sparse=False, force_serial=False),
+            op.to_matrix(sparse=False, force_serial=True),
+        )
+
     def test_to_matrix_parameters(self):
         """Test to_matrix method for parameterized SparsePauliOp."""
         labels = ["XI", "YZ", "YY", "ZZ"]

From 38ad0d106a6f10bf2bd09f9643c0fbb78f3f8446 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Sun, 28 Apr 2024 15:36:53 -0400
Subject: [PATCH 032/179] Removing the superfluous-parens lint rule and updates
 (#12303)

---
 pyproject.toml                                     |  1 -
 qiskit/quantum_info/operators/symplectic/random.py |  2 +-
 test/python/quantum_info/states/test_utils.py      | 12 ++++++------
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 40c6701fd05a..975bd377760c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -225,7 +225,6 @@ disable = [
     "no-member",
     "no-value-for-parameter",
     "not-context-manager",
-    "superfluous-parens",
     "unexpected-keyword-arg",
     "unnecessary-dict-index-lookup",
     "unnecessary-dunder-call",
diff --git a/qiskit/quantum_info/operators/symplectic/random.py b/qiskit/quantum_info/operators/symplectic/random.py
index 1a845100b91f..f9bd65ef9187 100644
--- a/qiskit/quantum_info/operators/symplectic/random.py
+++ b/qiskit/quantum_info/operators/symplectic/random.py
@@ -81,7 +81,7 @@ def random_pauli_list(
     z = rng.integers(2, size=(size, num_qubits)).astype(bool)
     x = rng.integers(2, size=(size, num_qubits)).astype(bool)
     if phase:
-        _phase = rng.integers(4, size=(size))
+        _phase = rng.integers(4, size=size)
         return PauliList.from_symplectic(z, x, _phase)
     return PauliList.from_symplectic(z, x)
 
diff --git a/test/python/quantum_info/states/test_utils.py b/test/python/quantum_info/states/test_utils.py
index 9a9015944e70..1382963ed554 100644
--- a/test/python/quantum_info/states/test_utils.py
+++ b/test/python/quantum_info/states/test_utils.py
@@ -113,14 +113,14 @@ def test_schmidt_decomposition_3_level_system(self):
 
         # check decomposition elements
         self.assertAlmostEqual(schmidt_comps[0][0], 1 / np.sqrt(3))
-        self.assertEqual(schmidt_comps[0][1], Statevector(np.array([1, 0, 0]), dims=(3)))
-        self.assertEqual(schmidt_comps[0][2], Statevector(np.array([1, 0, 0]), dims=(3)))
+        self.assertEqual(schmidt_comps[0][1], Statevector(np.array([1, 0, 0]), dims=3))
+        self.assertEqual(schmidt_comps[0][2], Statevector(np.array([1, 0, 0]), dims=3))
         self.assertAlmostEqual(schmidt_comps[1][0], 1 / np.sqrt(3))
-        self.assertEqual(schmidt_comps[1][1], Statevector(np.array([0, 1, 0]), dims=(3)))
-        self.assertEqual(schmidt_comps[1][2], Statevector(np.array([0, 1, 0]), dims=(3)))
+        self.assertEqual(schmidt_comps[1][1], Statevector(np.array([0, 1, 0]), dims=3))
+        self.assertEqual(schmidt_comps[1][2], Statevector(np.array([0, 1, 0]), dims=3))
         self.assertAlmostEqual(schmidt_comps[2][0], 1 / np.sqrt(3))
-        self.assertEqual(schmidt_comps[2][1], Statevector(np.array([0, 0, 1]), dims=(3)))
-        self.assertEqual(schmidt_comps[2][2], Statevector(np.array([0, 0, 1]), dims=(3)))
+        self.assertEqual(schmidt_comps[2][1], Statevector(np.array([0, 0, 1]), dims=3))
+        self.assertEqual(schmidt_comps[2][2], Statevector(np.array([0, 0, 1]), dims=3))
 
         # check that state can be properly reconstructed
         state = Statevector(

From 8194a68b69eed99be7735e3a699bdcf33250a7f0 Mon Sep 17 00:00:00 2001
From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Date: Mon, 29 Apr 2024 17:13:42 +0300
Subject: [PATCH 033/179] Add reverse permutation for LNN connectivity (#12181)

* add permutation reverse lnn function

* update init files

* update other synthesis functions

* add tests

* add release notes

* add helper function _append_reverse_permutation_lnn_kms

* add more cases to test
---
 qiskit/synthesis/__init__.py                  |  2 +
 qiskit/synthesis/linear_phase/cz_depth_lnn.py | 22 +----
 qiskit/synthesis/permutation/__init__.py      |  1 +
 .../permutation/permutation_reverse_lnn.py    | 90 +++++++++++++++++++
 qiskit/synthesis/qft/qft_decompose_lnn.py     |  8 +-
 ...erse-permutation-lnn-409a07c7f6d0eed9.yaml |  8 ++
 .../synthesis/test_permutation_synthesis.py   | 28 +++++-
 7 files changed, 133 insertions(+), 26 deletions(-)
 create mode 100644 qiskit/synthesis/permutation/permutation_reverse_lnn.py
 create mode 100644 releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml

diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py
index c191eac97472..49e7885d5096 100644
--- a/qiskit/synthesis/__init__.py
+++ b/qiskit/synthesis/__init__.py
@@ -51,6 +51,7 @@
 .. autofunction:: synth_permutation_depth_lnn_kms
 .. autofunction:: synth_permutation_basic
 .. autofunction:: synth_permutation_acg
+.. autofunction:: synth_permutation_reverse_lnn_kms
 
 Clifford Synthesis
 ==================
@@ -140,6 +141,7 @@
     synth_permutation_depth_lnn_kms,
     synth_permutation_basic,
     synth_permutation_acg,
+    synth_permutation_reverse_lnn_kms,
 )
 from .linear import (
     synth_cnot_count_full_pmh,
diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py
index b3931d078179..6dc7db5d619b 100644
--- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py
+++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py
@@ -24,24 +24,10 @@
 
 import numpy as np
 from qiskit.circuit import QuantumCircuit
-
-
-def _append_cx_stage1(qc, n):
-    """A single layer of CX gates."""
-    for i in range(n // 2):
-        qc.cx(2 * i, 2 * i + 1)
-    for i in range((n + 1) // 2 - 1):
-        qc.cx(2 * i + 2, 2 * i + 1)
-    return qc
-
-
-def _append_cx_stage2(qc, n):
-    """A single layer of CX gates."""
-    for i in range(n // 2):
-        qc.cx(2 * i + 1, 2 * i)
-    for i in range((n + 1) // 2 - 1):
-        qc.cx(2 * i + 1, 2 * i + 2)
-    return qc
+from qiskit.synthesis.permutation.permutation_reverse_lnn import (
+    _append_cx_stage1,
+    _append_cx_stage2,
+)
 
 
 def _odd_pattern1(n):
diff --git a/qiskit/synthesis/permutation/__init__.py b/qiskit/synthesis/permutation/__init__.py
index 7cc8d0174d71..5a8b9a7a13f8 100644
--- a/qiskit/synthesis/permutation/__init__.py
+++ b/qiskit/synthesis/permutation/__init__.py
@@ -15,3 +15,4 @@
 
 from .permutation_lnn import synth_permutation_depth_lnn_kms
 from .permutation_full import synth_permutation_basic, synth_permutation_acg
+from .permutation_reverse_lnn import synth_permutation_reverse_lnn_kms
diff --git a/qiskit/synthesis/permutation/permutation_reverse_lnn.py b/qiskit/synthesis/permutation/permutation_reverse_lnn.py
new file mode 100644
index 000000000000..26287a06177e
--- /dev/null
+++ b/qiskit/synthesis/permutation/permutation_reverse_lnn.py
@@ -0,0 +1,90 @@
+# 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.
+"""
+Synthesis of a reverse permutation for LNN connectivity.
+"""
+
+from qiskit.circuit import QuantumCircuit
+
+
+def _append_cx_stage1(qc, n):
+    """A single layer of CX gates."""
+    for i in range(n // 2):
+        qc.cx(2 * i, 2 * i + 1)
+    for i in range((n + 1) // 2 - 1):
+        qc.cx(2 * i + 2, 2 * i + 1)
+    return qc
+
+
+def _append_cx_stage2(qc, n):
+    """A single layer of CX gates."""
+    for i in range(n // 2):
+        qc.cx(2 * i + 1, 2 * i)
+    for i in range((n + 1) // 2 - 1):
+        qc.cx(2 * i + 1, 2 * i + 2)
+    return qc
+
+
+def _append_reverse_permutation_lnn_kms(qc: QuantumCircuit, num_qubits: int) -> None:
+    """
+    Append reverse permutation to a QuantumCircuit for linear nearest-neighbor architectures
+    using Kutin, Moulton, Smithline method.
+
+    Synthesis algorithm for reverse permutation from [1], section 5.
+    This algorithm synthesizes the reverse permutation on :math:`n` qubits over
+    a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.
+
+    Args:
+        qc: The original quantum circuit.
+        num_qubits: The number of qubits.
+
+    Returns:
+        The quantum circuit with appended reverse permutation.
+
+    References:
+        1. Kutin, S., Moulton, D. P., Smithline, L.,
+           *Computation at a distance*, Chicago J. Theor. Comput. Sci., vol. 2007, (2007),
+           `arXiv:quant-ph/0701194 `_
+    """
+
+    for _ in range((num_qubits + 1) // 2):
+        _append_cx_stage1(qc, num_qubits)
+        _append_cx_stage2(qc, num_qubits)
+    if (num_qubits % 2) == 0:
+        _append_cx_stage1(qc, num_qubits)
+
+
+def synth_permutation_reverse_lnn_kms(num_qubits: int) -> QuantumCircuit:
+    """
+    Synthesize reverse permutation for linear nearest-neighbor architectures using
+    Kutin, Moulton, Smithline method.
+
+    Synthesis algorithm for reverse permutation from [1], section 5.
+    This algorithm synthesizes the reverse permutation on :math:`n` qubits over
+    a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.
+
+    Args:
+        num_qubits: The number of qubits.
+
+    Returns:
+        The synthesized quantum circuit.
+
+    References:
+        1. Kutin, S., Moulton, D. P., Smithline, L.,
+           *Computation at a distance*, Chicago J. Theor. Comput. Sci., vol. 2007, (2007),
+           `arXiv:quant-ph/0701194 `_
+    """
+
+    qc = QuantumCircuit(num_qubits)
+    _append_reverse_permutation_lnn_kms(qc, num_qubits)
+
+    return qc
diff --git a/qiskit/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py
index 4dd8d9d56d13..a54be481f51b 100644
--- a/qiskit/synthesis/qft/qft_decompose_lnn.py
+++ b/qiskit/synthesis/qft/qft_decompose_lnn.py
@@ -15,7 +15,7 @@
 
 import numpy as np
 from qiskit.circuit import QuantumCircuit
-from qiskit.synthesis.linear_phase.cz_depth_lnn import _append_cx_stage1, _append_cx_stage2
+from qiskit.synthesis.permutation.permutation_reverse_lnn import _append_reverse_permutation_lnn_kms
 
 
 def synth_qft_line(
@@ -65,10 +65,6 @@ def synth_qft_line(
     if not do_swaps:
         # Add a reversal network for LNN connectivity in depth 2*n+2,
         # based on Kutin at al., https://arxiv.org/abs/quant-ph/0701194, Section 5.
-        for _ in range((num_qubits + 1) // 2):
-            qc = _append_cx_stage1(qc, num_qubits)
-            qc = _append_cx_stage2(qc, num_qubits)
-        if (num_qubits % 2) == 0:
-            qc = _append_cx_stage1(qc, num_qubits)
+        _append_reverse_permutation_lnn_kms(qc, num_qubits)
 
     return qc
diff --git a/releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml b/releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml
new file mode 100644
index 000000000000..357345adfa26
--- /dev/null
+++ b/releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml
@@ -0,0 +1,8 @@
+---
+features_synthesis:
+  - |
+    Add a new synthesis method :func:`.synth_permutation_reverse_lnn_kms`
+    of reverse permutations for linear nearest-neighbor architectures using
+    Kutin, Moulton, Smithline method.
+    This algorithm synthesizes the reverse permutation on :math:`n` qubits over
+    a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.
diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py
index 7fc6f5e24ab8..5c4317ed58a3 100644
--- a/test/python/synthesis/test_permutation_synthesis.py
+++ b/test/python/synthesis/test_permutation_synthesis.py
@@ -19,8 +19,12 @@
 
 from qiskit.quantum_info.operators import Operator
 from qiskit.circuit.library import LinearFunction, PermutationGate
-from qiskit.synthesis import synth_permutation_acg
-from qiskit.synthesis.permutation import synth_permutation_depth_lnn_kms, synth_permutation_basic
+from qiskit.synthesis.permutation import (
+    synth_permutation_acg,
+    synth_permutation_depth_lnn_kms,
+    synth_permutation_basic,
+    synth_permutation_reverse_lnn_kms,
+)
 from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
@@ -108,6 +112,26 @@ def test_synth_permutation_depth_lnn_kms(self, width):
             synthesized_pattern = LinearFunction(qc).permutation_pattern()
             self.assertTrue(np.array_equal(synthesized_pattern, pattern))
 
+    @data(1, 2, 3, 4, 5, 10, 15, 20)
+    def test_synth_permutation_reverse_lnn_kms(self, num_qubits):
+        """Test synth_permutation_reverse_lnn_kms function produces the correct
+        circuit."""
+        pattern = list(reversed(range(num_qubits)))
+        qc = synth_permutation_reverse_lnn_kms(num_qubits)
+        self.assertListEqual((LinearFunction(qc).permutation_pattern()).tolist(), pattern)
+
+        # Check that the CX depth of the circuit is at 2*n+2
+        self.assertTrue(qc.depth() <= 2 * num_qubits + 2)
+
+        # Check that the synthesized circuit consists of CX gates only,
+        # and that these CXs adhere to the LNN connectivity.
+        for instruction in qc.data:
+            self.assertEqual(instruction.operation.name, "cx")
+            q0 = qc.find_bit(instruction.qubits[0]).index
+            q1 = qc.find_bit(instruction.qubits[1]).index
+            dist = abs(q0 - q1)
+            self.assertEqual(dist, 1)
+
     @data(4, 5, 6, 7)
     def test_permutation_matrix(self, width):
         """Test that the unitary matrix constructed from permutation pattern

From 393524f0abc39f57108c26f716617f7ce4005d99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?=
 <57907331+ElePT@users.noreply.github.com>
Date: Mon, 29 Apr 2024 17:28:37 +0200
Subject: [PATCH 034/179] Rework handling of instruction durations in preset
 pass managers (#12183)

* Rework use of instruction durations, move logic from transpile function to individual passes.

* Apply review feedback on reno
---
 qiskit/compiler/transpiler.py                 | 78 +++----------------
 .../passes/scheduling/dynamical_decoupling.py | 27 ++++++-
 .../padding/dynamical_decoupling.py           | 26 ++++++-
 .../passes/scheduling/time_unit_conversion.py | 32 +++++++-
 ...nst-durations-passes-28c78401682e22c0.yaml | 15 ++++
 5 files changed, 103 insertions(+), 75 deletions(-)
 create mode 100644 releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml

diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py
index 5514bb168fa5..95c583ceeaad 100644
--- a/qiskit/compiler/transpiler.py
+++ b/qiskit/compiler/transpiler.py
@@ -337,7 +337,7 @@ def callback_func(**kwargs):
 
     _skip_target = False
     _given_inst_map = bool(inst_map)  # check before inst_map is overwritten
-    # If a target is specified have it override any implicit selections from a backend
+    # If a target is specified, have it override any implicit selections from a backend
     if target is not None:
         if coupling_map is None:
             coupling_map = target.build_coupling_map()
@@ -354,7 +354,7 @@ def callback_func(**kwargs):
         if backend_properties is None:
             backend_properties = target_to_backend_properties(target)
     # If target is not specified and any hardware constraint object is
-    # manually specified then do not use the target from the backend as
+    # manually specified, do not use the target from the backend as
     # it is invalidated by a custom basis gate list, custom coupling map,
     # custom dt or custom instruction_durations
     elif (
@@ -379,6 +379,7 @@ def callback_func(**kwargs):
     _check_circuits_coupling_map(circuits, coupling_map, backend)
 
     timing_constraints = _parse_timing_constraints(backend, timing_constraints)
+    instruction_durations = _parse_instruction_durations(backend, instruction_durations, dt)
 
     if _given_inst_map and inst_map.has_custom_gate() and target is not None:
         # Do not mutate backend target
@@ -391,51 +392,6 @@ def callback_func(**kwargs):
         if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
             translation_method = backend.get_translation_stage_plugin()
 
-    if instruction_durations or dt:
-        # If durations are provided and there is more than one circuit
-        # we need to serialize the execution because the full durations
-        # is dependent on the circuit calibrations which are per circuit
-        if len(circuits) > 1:
-            out_circuits = []
-            for circuit in circuits:
-                instruction_durations = _parse_instruction_durations(
-                    backend, instruction_durations, dt, circuit
-                )
-                pm = generate_preset_pass_manager(
-                    optimization_level,
-                    backend=backend,
-                    target=target,
-                    basis_gates=basis_gates,
-                    inst_map=inst_map,
-                    coupling_map=coupling_map,
-                    instruction_durations=instruction_durations,
-                    backend_properties=backend_properties,
-                    timing_constraints=timing_constraints,
-                    initial_layout=initial_layout,
-                    layout_method=layout_method,
-                    routing_method=routing_method,
-                    translation_method=translation_method,
-                    scheduling_method=scheduling_method,
-                    approximation_degree=approximation_degree,
-                    seed_transpiler=seed_transpiler,
-                    unitary_synthesis_method=unitary_synthesis_method,
-                    unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
-                    hls_config=hls_config,
-                    init_method=init_method,
-                    optimization_method=optimization_method,
-                    _skip_target=_skip_target,
-                )
-                out_circuits.append(pm.run(circuit, callback=callback, num_processes=num_processes))
-            for name, circ in zip(output_name, out_circuits):
-                circ.name = name
-                end_time = time()
-            _log_transpile_time(start_time, end_time)
-            return out_circuits
-        else:
-            instruction_durations = _parse_instruction_durations(
-                backend, instruction_durations, dt, circuits[0]
-            )
-
     pm = generate_preset_pass_manager(
         optimization_level,
         backend=backend,
@@ -460,7 +416,7 @@ def callback_func(**kwargs):
         optimization_method=optimization_method,
         _skip_target=_skip_target,
     )
-    out_circuits = pm.run(circuits, callback=callback)
+    out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)
     for name, circ in zip(output_name, out_circuits):
         circ.name = name
     end_time = time()
@@ -535,32 +491,20 @@ def _parse_initial_layout(initial_layout):
     return initial_layout
 
 
-def _parse_instruction_durations(backend, inst_durations, dt, circuit):
+def _parse_instruction_durations(backend, inst_durations, dt):
     """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided,
     the backend will be ignored, otherwise, the durations will be populated from the
-    backend. If any circuits have gate calibrations, those calibration durations would
-    take precedence over backend durations, but be superceded by ``inst_duration``s.
+    backend.
     """
+    final_durations = InstructionDurations()
     if not inst_durations:
         backend_durations = InstructionDurations()
         if backend is not None:
             backend_durations = backend.instruction_durations
-
-    circ_durations = InstructionDurations()
-    if not inst_durations:
-        circ_durations.update(backend_durations, dt or backend_durations.dt)
-
-    if circuit.calibrations:
-        cal_durations = []
-        for gate, gate_cals in circuit.calibrations.items():
-            for (qubits, parameters), schedule in gate_cals.items():
-                cal_durations.append((gate, qubits, parameters, schedule.duration))
-        circ_durations.update(cal_durations, circ_durations.dt)
-
-    if inst_durations:
-        circ_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None))
-
-    return circ_durations
+        final_durations.update(backend_durations, dt or backend_durations.dt)
+    else:
+        final_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None))
+    return final_durations
 
 
 def _parse_approximation_degree(approximation_degree):
diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py
index 5b84b529e453..12f4bc515b29 100644
--- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py
+++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.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
@@ -20,6 +20,7 @@
 from qiskit.dagcircuit import DAGOpNode, DAGInNode
 from qiskit.quantum_info.operators.predicates import matrix_equal
 from qiskit.synthesis.one_qubit import OneQubitEulerDecomposer
+from qiskit.transpiler import InstructionDurations
 from qiskit.transpiler.passes.optimization import Optimize1qGates
 from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.transpiler.exceptions import TranspilerError
@@ -168,6 +169,8 @@ def run(self, dag):
         if dag.duration is None:
             raise TranspilerError("DD runs after circuit is scheduled.")
 
+        durations = self._update_inst_durations(dag)
+
         num_pulses = len(self._dd_sequence)
         sequence_gphase = 0
         if num_pulses != 1:
@@ -208,7 +211,7 @@ def run(self, dag):
             for index, gate in enumerate(self._dd_sequence):
                 gate = gate.to_mutable()
                 self._dd_sequence[index] = gate
-                gate.duration = self._durations.get(gate, physical_qubit)
+                gate.duration = durations.get(gate, physical_qubit)
 
                 dd_sequence_duration += gate.duration
             index_sequence_duration_map[physical_qubit] = dd_sequence_duration
@@ -277,6 +280,26 @@ def run(self, dag):
 
         return new_dag
 
+    def _update_inst_durations(self, dag):
+        """Update instruction durations with circuit information. If the dag contains gate
+        calibrations and no instruction durations were provided through the target or as a
+        standalone input, the circuit calibration durations will be used.
+        The priority order for instruction durations is: target > standalone > circuit.
+        """
+        circ_durations = InstructionDurations()
+
+        if dag.calibrations:
+            cal_durations = []
+            for gate, gate_cals in dag.calibrations.items():
+                for (qubits, parameters), schedule in gate_cals.items():
+                    cal_durations.append((gate, qubits, parameters, schedule.duration))
+            circ_durations.update(cal_durations, circ_durations.dt)
+
+        if self._durations is not None:
+            circ_durations.update(self._durations, getattr(self._durations, "dt", None))
+
+        return circ_durations
+
     def __gate_supported(self, gate: Gate, qarg: int) -> bool:
         """A gate is supported on the qubit (qarg) or not."""
         if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)):
diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
index 42a1bdc80f14..7cb309dd9aa1 100644
--- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
+++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.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
@@ -179,9 +179,31 @@ def __init__(
                         f"{gate.name} in dd_sequence is not supported in the target"
                     )
 
+    def _update_inst_durations(self, dag):
+        """Update instruction durations with circuit information. If the dag contains gate
+        calibrations and no instruction durations were provided through the target or as a
+        standalone input, the circuit calibration durations will be used.
+        The priority order for instruction durations is: target > standalone > circuit.
+        """
+        circ_durations = InstructionDurations()
+
+        if dag.calibrations:
+            cal_durations = []
+            for gate, gate_cals in dag.calibrations.items():
+                for (qubits, parameters), schedule in gate_cals.items():
+                    cal_durations.append((gate, qubits, parameters, schedule.duration))
+            circ_durations.update(cal_durations, circ_durations.dt)
+
+        if self._durations is not None:
+            circ_durations.update(self._durations, getattr(self._durations, "dt", None))
+
+        return circ_durations
+
     def _pre_runhook(self, dag: DAGCircuit):
         super()._pre_runhook(dag)
 
+        durations = self._update_inst_durations(dag)
+
         num_pulses = len(self._dd_sequence)
 
         # Check if physical circuit is given
@@ -245,7 +267,7 @@ def _pre_runhook(self, dag: DAGCircuit):
                             f"is not acceptable in {self.__class__.__name__} pass."
                         )
                 except KeyError:
-                    gate_length = self._durations.get(gate, physical_index)
+                    gate_length = durations.get(gate, physical_index)
                 sequence_lengths.append(gate_length)
                 # Update gate duration. This is necessary for current timeline drawer, i.e. scheduled.
                 gate = gate.to_mutable()
diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py
index d53c3fc4ef6a..25672c137f34 100644
--- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py
+++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.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
@@ -51,6 +51,7 @@ def __init__(self, inst_durations: InstructionDurations = None, target: Target =
         self.inst_durations = inst_durations or InstructionDurations()
         if target is not None:
             self.inst_durations = target.durations()
+        self._durations_provided = inst_durations is not None or target is not None
 
     def run(self, dag: DAGCircuit):
         """Run the TimeUnitAnalysis pass on `dag`.
@@ -64,8 +65,11 @@ def run(self, dag: DAGCircuit):
         Raises:
             TranspilerError: if the units are not unifiable
         """
+
+        inst_durations = self._update_inst_durations(dag)
+
         # Choose unit
-        if self.inst_durations.dt is not None:
+        if inst_durations.dt is not None:
             time_unit = "dt"
         else:
             # Check what units are used in delays and other instructions: dt or SI or mixed
@@ -75,7 +79,7 @@ def run(self, dag: DAGCircuit):
                     "Fail to unify time units in delays. SI units "
                     "and dt unit must not be mixed when dt is not supplied."
                 )
-            units_other = self.inst_durations.units_used()
+            units_other = inst_durations.units_used()
             if self._unified(units_other) == "mixed":
                 raise TranspilerError(
                     "Fail to unify time units in instruction_durations. SI units "
@@ -96,7 +100,7 @@ def run(self, dag: DAGCircuit):
         # Make units consistent
         for node in dag.op_nodes():
             try:
-                duration = self.inst_durations.get(
+                duration = inst_durations.get(
                     node.op, [dag.find_bit(qarg).index for qarg in node.qargs], unit=time_unit
                 )
             except TranspilerError:
@@ -108,6 +112,26 @@ def run(self, dag: DAGCircuit):
         self.property_set["time_unit"] = time_unit
         return dag
 
+    def _update_inst_durations(self, dag):
+        """Update instruction durations with circuit information. If the dag contains gate
+        calibrations and no instruction durations were provided through the target or as a
+        standalone input, the circuit calibration durations will be used.
+        The priority order for instruction durations is: target > standalone > circuit.
+        """
+        circ_durations = InstructionDurations()
+
+        if dag.calibrations:
+            cal_durations = []
+            for gate, gate_cals in dag.calibrations.items():
+                for (qubits, parameters), schedule in gate_cals.items():
+                    cal_durations.append((gate, qubits, parameters, schedule.duration))
+            circ_durations.update(cal_durations, circ_durations.dt)
+
+        if self._durations_provided:
+            circ_durations.update(self.inst_durations, getattr(self.inst_durations, "dt", None))
+
+        return circ_durations
+
     @staticmethod
     def _units_used_in_delays(dag: DAGCircuit) -> Set[str]:
         units_used = set()
diff --git a/releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml b/releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml
new file mode 100644
index 000000000000..2ccd92f19c14
--- /dev/null
+++ b/releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml
@@ -0,0 +1,15 @@
+---
+fixes:
+  - |
+    The internal handling of custom circuit calibrations and :class:`.InstructionDurations`
+    has been offloaded from the :func:`.transpile` function to the individual transpiler passes: 
+    :class:`qiskit.transpiler.passes.scheduling.DynamicalDecoupling`,
+    :class:`qiskit.transpiler.passes.scheduling.padding.DynamicalDecoupling`. Before, 
+    instruction durations from circuit calibrations would not be taken into account unless 
+    they were manually incorporated into `instruction_durations` input argument, but the passes
+    that need it now analyze the circuit and pick the most relevant duration value according 
+    to the following priority order: target > custom input > circuit calibrations.
+
+  - |
+    Fixed a bug in :func:`.transpile` where the ``num_processes`` argument would only be used
+    if ``dt`` or ``instruction_durations`` were provided. 
\ No newline at end of file

From b442b1c4fd68628f140ba5dd38e8d1a6577f2eeb Mon Sep 17 00:00:00 2001
From: Arthur Strauss <56998701+arthurostrauss@users.noreply.github.com>
Date: Mon, 29 Apr 2024 18:34:25 +0200
Subject: [PATCH 035/179] Assignment of parameters in pulse
 `Schedule`/`ScheduleBlock` doable through parameter name (#12088)

* Added possibility of assigning Parameter and ParameterVector by name

It is now possible to specify in the mapping the names of the parameters instead of the parameters themselves to assign parameters to pulse `Schedule`s and `ScheduleBlock`s. It is even possible to assign all the parameters of a ParameterVector by just specifying its name and setting as a value a list of parameter values.

* Update parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml

Co-authored-by: Will Shanks 

* Reshaped string parameter assignment

Corrected mistake in string assignment

Corrected mistake in string assignment

* Update releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml

Co-authored-by: Will Shanks 

* Enabled string assignment for multiple params carrying same name

The check is now based on the value type to infer if assignment should be done on Parameters or ParameterVectors

Removed unnecessary import from utils

Corrected string assignment

Correction part 2

Corrected test

The inplace=True argument was preventing the reuse of a parametrized waveform in the schedule, making the test fail

* Reformat

---------

Co-authored-by: Will Shanks 
---
 qiskit/pulse/parameter_manager.py             | 45 ++++++++++++-------
 qiskit/pulse/schedule.py                      | 16 ++++---
 qiskit/pulse/utils.py                         | 35 ++++++++++++++-
 ..._for_pulse_schedules-3a27bbbbf235fb9e.yaml |  8 ++++
 test/python/pulse/test_parameter_manager.py   | 38 ++++++++++++++++
 5 files changed, 118 insertions(+), 24 deletions(-)
 create mode 100644 releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml

diff --git a/qiskit/pulse/parameter_manager.py b/qiskit/pulse/parameter_manager.py
index 561eac01f55d..e5a4a1a1d2bd 100644
--- a/qiskit/pulse/parameter_manager.py
+++ b/qiskit/pulse/parameter_manager.py
@@ -54,7 +54,7 @@
 from copy import copy
 from typing import Any, Mapping, Sequence
 
-from qiskit.circuit import ParameterVector
+from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
 from qiskit.circuit.parameter import Parameter
 from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
 from qiskit.pulse import instructions, channels
@@ -62,7 +62,11 @@
 from qiskit.pulse.library import SymbolicPulse, Waveform
 from qiskit.pulse.schedule import Schedule, ScheduleBlock
 from qiskit.pulse.transforms.alignments import AlignmentKind
-from qiskit.pulse.utils import format_parameter_value
+from qiskit.pulse.utils import (
+    format_parameter_value,
+    _validate_parameter_vector,
+    _validate_parameter_value,
+)
 
 
 class NodeVisitor:
@@ -362,7 +366,8 @@ def assign_parameters(
         self,
         pulse_program: Any,
         value_dict: dict[
-            ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
+            ParameterExpression | ParameterVector | str,
+            ParameterValueType | Sequence[ParameterValueType],
         ],
     ) -> Any:
         """Modify and return program data with parameters assigned according to the input.
@@ -397,7 +402,7 @@ def update_parameter_table(self, new_node: Any):
     def _unroll_param_dict(
         self,
         parameter_binds: Mapping[
-            Parameter | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
+            Parameter | ParameterVector | str, ParameterValueType | Sequence[ParameterValueType]
         ],
     ) -> Mapping[Parameter, ParameterValueType]:
         """
@@ -410,21 +415,31 @@ def _unroll_param_dict(
             A dictionary from parameter to value.
         """
         out = {}
+        param_name_dict = {param.name: [] for param in self.parameters}
+        for param in self.parameters:
+            param_name_dict[param.name].append(param)
+        param_vec_dict = {
+            param.vector.name: param.vector
+            for param in self.parameters
+            if isinstance(param, ParameterVectorElement)
+        }
+        for name in param_vec_dict.keys():
+            if name in param_name_dict:
+                param_name_dict[name].append(param_vec_dict[name])
+            else:
+                param_name_dict[name] = [param_vec_dict[name]]
+
         for parameter, value in parameter_binds.items():
             if isinstance(parameter, ParameterVector):
-                if not isinstance(value, Sequence):
-                    raise PulseError(
-                        f"Parameter vector '{parameter.name}' has length {len(parameter)},"
-                        f" but was assigned to a single value."
-                    )
-                if len(parameter) != len(value):
-                    raise PulseError(
-                        f"Parameter vector '{parameter.name}' has length {len(parameter)},"
-                        f" but was assigned to {len(value)} values."
-                    )
+                _validate_parameter_vector(parameter, value)
                 out.update(zip(parameter, value))
             elif isinstance(parameter, str):
-                out[self.get_parameters(parameter)] = value
+                for param in param_name_dict[parameter]:
+                    is_vec = _validate_parameter_value(param, value)
+                    if is_vec:
+                        out.update(zip(param, value))
+                    else:
+                        out[param] = value
             else:
                 out[parameter] = value
         return out
diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py
index a4d1ad844e5f..5241da0c31d1 100644
--- a/qiskit/pulse/schedule.py
+++ b/qiskit/pulse/schedule.py
@@ -715,16 +715,17 @@ def is_parameterized(self) -> bool:
     def assign_parameters(
         self,
         value_dict: dict[
-            ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
+            ParameterExpression | ParameterVector | str,
+            ParameterValueType | Sequence[ParameterValueType],
         ],
         inplace: bool = True,
     ) -> "Schedule":
         """Assign the parameters in this schedule according to the input.
 
         Args:
-            value_dict: A mapping from parameters (parameter vectors) to either
-            numeric values (list of numeric values)
-            or another Parameter expression (list of Parameter expressions).
+            value_dict: A mapping from parameters or parameter names (parameter vector
+            or parameter vector name) to either numeric values (list of numeric values)
+            or another parameter expression (list of parameter expressions).
             inplace: Set ``True`` to override this instance with new parameter.
 
         Returns:
@@ -1416,15 +1417,16 @@ def is_referenced(self) -> bool:
     def assign_parameters(
         self,
         value_dict: dict[
-            ParameterExpression | ParameterVector, ParameterValueType | Sequence[ParameterValueType]
+            ParameterExpression | ParameterVector | str,
+            ParameterValueType | Sequence[ParameterValueType],
         ],
         inplace: bool = True,
     ) -> "ScheduleBlock":
         """Assign the parameters in this schedule according to the input.
 
         Args:
-            value_dict: A mapping from parameters (parameter vectors) to either numeric values
-            (list of numeric values)
+            value_dict: A mapping from parameters or parameter names (parameter vector
+            or parameter vector name) to either numeric values (list of numeric values)
             or another parameter expression (list of parameter expressions).
             inplace: Set ``True`` to override this instance with new parameter.
 
diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py
index fddc9469add9..ae87fbafadde 100644
--- a/qiskit/pulse/utils.py
+++ b/qiskit/pulse/utils.py
@@ -11,13 +11,14 @@
 # that they have been altered from the originals.
 
 """Module for common pulse programming utilities."""
-from typing import List, Dict, Union
+from typing import List, Dict, Union, Sequence
 import warnings
 
 import numpy as np
 
+from qiskit.circuit import ParameterVector, Parameter
 from qiskit.circuit.parameterexpression import ParameterExpression
-from qiskit.pulse.exceptions import UnassignedDurationError, QiskitError
+from qiskit.pulse.exceptions import UnassignedDurationError, QiskitError, PulseError
 
 
 def format_meas_map(meas_map: List[List[int]]) -> Dict[int, List[int]]:
@@ -117,3 +118,33 @@ def instruction_duration_validation(duration: int):
         raise QiskitError(
             f"Instruction duration must be a non-negative integer, got {duration} instead."
         )
+
+
+def _validate_parameter_vector(parameter: ParameterVector, value):
+    """Validate parameter vector and its value."""
+    if not isinstance(value, Sequence):
+        raise PulseError(
+            f"Parameter vector '{parameter.name}' has length {len(parameter)},"
+            f" but was assigned to {value}."
+        )
+    if len(parameter) != len(value):
+        raise PulseError(
+            f"Parameter vector '{parameter.name}' has length {len(parameter)},"
+            f" but was assigned to {len(value)} values."
+        )
+
+
+def _validate_single_parameter(parameter: Parameter, value):
+    """Validate single parameter and its value."""
+    if not isinstance(value, (int, float, complex, ParameterExpression)):
+        raise PulseError(f"Parameter '{parameter.name}' is not assignable to {value}.")
+
+
+def _validate_parameter_value(parameter, value):
+    """Validate parameter and its value."""
+    if isinstance(parameter, ParameterVector):
+        _validate_parameter_vector(parameter, value)
+        return True
+    else:
+        _validate_single_parameter(parameter, value)
+        return False
diff --git a/releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml b/releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml
new file mode 100644
index 000000000000..551ea9e918c6
--- /dev/null
+++ b/releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml
@@ -0,0 +1,8 @@
+---
+features_pulse:
+  - |
+    It is now possible to assign parameters to pulse :class:`.Schedule`and :class:`.ScheduleBlock` objects by specifying
+    the parameter name as a string. The parameter name can be used to assign values to all parameters within the
+    `Schedule` or `ScheduleBlock` that have the same name. Moreover, the parameter name of a `ParameterVector`
+    can be used to assign all values of the vector simultaneously (the list of values should therefore match the
+    length of the vector).
diff --git a/test/python/pulse/test_parameter_manager.py b/test/python/pulse/test_parameter_manager.py
index 54268af14577..0b91aaeaab4a 100644
--- a/test/python/pulse/test_parameter_manager.py
+++ b/test/python/pulse/test_parameter_manager.py
@@ -515,6 +515,44 @@ def test_parametric_pulses_with_parameter_vector(self):
         self.assertEqual(sched2.instructions[0][1].pulse.sigma, 4.0)
         self.assertEqual(sched2.instructions[1][1].phase, 0.1)
 
+    def test_pulse_assignment_with_parameter_names(self):
+        """Test pulse assignment with parameter names."""
+        sigma = Parameter("sigma")
+        amp = Parameter("amp")
+        param_vec = ParameterVector("param_vec", 2)
+
+        waveform = pulse.library.Gaussian(duration=128, sigma=sigma, amp=amp)
+        waveform2 = pulse.library.Gaussian(duration=128, sigma=40, amp=amp)
+        block = pulse.ScheduleBlock()
+        block += pulse.Play(waveform, pulse.DriveChannel(10))
+        block += pulse.Play(waveform2, pulse.DriveChannel(10))
+        block += pulse.ShiftPhase(param_vec[0], pulse.DriveChannel(10))
+        block += pulse.ShiftPhase(param_vec[1], pulse.DriveChannel(10))
+        block1 = block.assign_parameters(
+            {"amp": 0.2, "sigma": 4, "param_vec": [3.14, 1.57]}, inplace=False
+        )
+
+        self.assertEqual(block1.blocks[0].pulse.amp, 0.2)
+        self.assertEqual(block1.blocks[0].pulse.sigma, 4.0)
+        self.assertEqual(block1.blocks[1].pulse.amp, 0.2)
+        self.assertEqual(block1.blocks[2].phase, 3.14)
+        self.assertEqual(block1.blocks[3].phase, 1.57)
+
+        sched = pulse.Schedule()
+        sched += pulse.Play(waveform, pulse.DriveChannel(10))
+        sched += pulse.Play(waveform2, pulse.DriveChannel(10))
+        sched += pulse.ShiftPhase(param_vec[0], pulse.DriveChannel(10))
+        sched += pulse.ShiftPhase(param_vec[1], pulse.DriveChannel(10))
+        sched1 = sched.assign_parameters(
+            {"amp": 0.2, "sigma": 4, "param_vec": [3.14, 1.57]}, inplace=False
+        )
+
+        self.assertEqual(sched1.instructions[0][1].pulse.amp, 0.2)
+        self.assertEqual(sched1.instructions[0][1].pulse.sigma, 4.0)
+        self.assertEqual(sched1.instructions[1][1].pulse.amp, 0.2)
+        self.assertEqual(sched1.instructions[2][1].phase, 3.14)
+        self.assertEqual(sched1.instructions[3][1].phase, 1.57)
+
 
 class TestScheduleTimeslots(QiskitTestCase):
     """Test for edge cases of timing overlap on parametrized channels.

From aacd4935f4cacfdabaa1179b0fe90496148f658e Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Tue, 30 Apr 2024 03:07:31 +0100
Subject: [PATCH 036/179] Have `QuantumCircuit.store` do integer-literal type
 promotion (#12201)

* Have `QuantumCircuit.store` do integer-literal type promotion

`QuantumCircuit.store` does convenience lifting of arbitary values to
`expr.Expr` nodes.  Similar to the binary-operation creators, it's
convenient to have `QuantumCircuit.store` correctly infer the required
widths of integer literals in the rvalue.

This isn't full type inference, just a small amount of convenience.

* Add integer-literal widening to `add_var`

* Add no-widen test of `QuantumCircuit.add_var`
---
 qiskit/circuit/quantumcircuit.py         | 21 +++++++++++++++++--
 test/python/circuit/test_circuit_vars.py | 26 +++++++++++++++++++++++-
 test/python/circuit/test_store.py        | 16 +++++++++++++++
 3 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index f25dca4b03b4..b19269a49166 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1765,7 +1765,18 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V
         # Validate the initialiser first to catch cases where the variable to be declared is being
         # used in the initialiser.
         circuit_scope = self._current_scope()
-        initial = _validate_expr(circuit_scope, expr.lift(initial))
+        # Convenience method to widen Python integer literals to the right width during the initial
+        # lift, if the type is already known via the variable.
+        if (
+            isinstance(name_or_var, expr.Var)
+            and name_or_var.type.kind is types.Uint
+            and isinstance(initial, int)
+            and not isinstance(initial, bool)
+        ):
+            coerce_type = name_or_var.type
+        else:
+            coerce_type = None
+        initial = _validate_expr(circuit_scope, expr.lift(initial, coerce_type))
         if isinstance(name_or_var, str):
             var = expr.Var.new(name_or_var, initial.type)
         elif not name_or_var.standalone:
@@ -2669,7 +2680,13 @@ def store(self, lvalue: typing.Any, rvalue: typing.Any, /) -> InstructionSet:
             :meth:`add_var`
                 Create a new variable in the circuit that can be written to with this method.
         """
-        return self.append(Store(expr.lift(lvalue), expr.lift(rvalue)), (), (), copy=False)
+        # As a convenience, lift integer-literal rvalues to the matching width.
+        lvalue = expr.lift(lvalue)
+        rvalue_type = (
+            lvalue.type if isinstance(rvalue, int) and not isinstance(rvalue, bool) else None
+        )
+        rvalue = expr.lift(rvalue, rvalue_type)
+        return self.append(Store(lvalue, rvalue), (), (), copy=False)
 
     def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet:
         r"""Measure a quantum bit (``qubit``) in the Z basis into a classical bit (``cbit``).
diff --git a/test/python/circuit/test_circuit_vars.py b/test/python/circuit/test_circuit_vars.py
index 0da541085366..8b7167eed7e1 100644
--- a/test/python/circuit/test_circuit_vars.py
+++ b/test/python/circuit/test_circuit_vars.py
@@ -14,7 +14,7 @@
 
 from test import QiskitTestCase
 
-from qiskit.circuit import QuantumCircuit, CircuitError, Clbit, ClassicalRegister
+from qiskit.circuit import QuantumCircuit, CircuitError, Clbit, ClassicalRegister, Store
 from qiskit.circuit.classical import expr, types
 
 
@@ -241,6 +241,30 @@ def test_initialise_declarations_equal_to_add_var(self):
         self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars()))
         self.assertEqual(qc_init.data, qc_manual.data)
 
+    def test_declarations_widen_integer_literals(self):
+        a = expr.Var.new("a", types.Uint(8))
+        b = expr.Var.new("b", types.Uint(16))
+        qc = QuantumCircuit(declarations=[(a, 3)])
+        qc.add_var(b, 5)
+        actual_initializers = [
+            (op.lvalue, op.rvalue)
+            for instruction in qc
+            if isinstance((op := instruction.operation), Store)
+        ]
+        expected_initializers = [
+            (a, expr.Value(3, types.Uint(8))),
+            (b, expr.Value(5, types.Uint(16))),
+        ]
+        self.assertEqual(actual_initializers, expected_initializers)
+
+    def test_declaration_does_not_widen_bool_literal(self):
+        # `bool` is a subclass of `int` in Python (except some arithmetic operations have different
+        # semantics...).  It's not in Qiskit's value type system, though.
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit()
+        with self.assertRaisesRegex(CircuitError, "explicit cast is required"):
+            qc.add_var(a, True)
+
     def test_cannot_shadow_vars(self):
         """Test that exact duplicate ``Var`` nodes within different combinations of the inputs are
         detected and rejected."""
diff --git a/test/python/circuit/test_store.py b/test/python/circuit/test_store.py
index b44aac51f7a5..425eae55a4bf 100644
--- a/test/python/circuit/test_store.py
+++ b/test/python/circuit/test_store.py
@@ -133,6 +133,22 @@ def test_lifts_values(self):
         qc.store(b, 0xFFFF)
         self.assertEqual(qc.data[-1].operation, Store(b, expr.lift(0xFFFF)))
 
+    def test_lifts_integer_literals_to_full_width(self):
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(inputs=[a])
+        qc.store(a, 1)
+        self.assertEqual(qc.data[-1].operation, Store(a, expr.Value(1, a.type)))
+        qc.store(a, 255)
+        self.assertEqual(qc.data[-1].operation, Store(a, expr.Value(255, a.type)))
+
+    def test_does_not_widen_bool_literal(self):
+        # `bool` is a subclass of `int` in Python (except some arithmetic operations have different
+        # semantics...).  It's not in Qiskit's value type system, though.
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(captures=[a])
+        with self.assertRaisesRegex(CircuitError, "explicit cast is required"):
+            qc.store(a, True)
+
     def test_rejects_vars_not_in_circuit(self):
         a = expr.Var.new("a", types.Bool())
         b = expr.Var.new("b", types.Bool())

From 7ec2c56c068a3eda8608fce04e648c1f95b1dc6f Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Tue, 30 Apr 2024 08:56:51 +0200
Subject: [PATCH 037/179] Extend the basis gates of BasicSimulator (#12186)

* extend the basis gates of BasicSimulator

* remove variational sized gates

* reno and test

* accidentally missing u2 and u1

* three qubits support

* support for parameters

* support for all the standard gates upto 3 qubits

* test

* adding rccx

* missing gates

* test notes

* readjust test

* check target

* tt

* dict lookup

* reno
---
 qiskit/circuit/library/standard_gates/x.py    |  14 +-
 .../basic_provider/basic_provider_tools.py    |  98 +++-
 .../basic_provider/basic_simulator.py         |  91 ++-
 .../notes/fixes_10852-e197344c5f44b4f1.yaml   |   5 +
 .../basic_provider/test_standard_library.py   | 531 ++++++++++++++++++
 .../transpiler/test_passmanager_config.py     | 106 ++--
 6 files changed, 757 insertions(+), 88 deletions(-)
 create mode 100644 releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml
 create mode 100644 test/python/providers/basic_provider/test_standard_library.py

diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py
index c0eb505efba0..7195df90dc98 100644
--- a/qiskit/circuit/library/standard_gates/x.py
+++ b/qiskit/circuit/library/standard_gates/x.py
@@ -107,7 +107,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
@@ -250,7 +250,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
@@ -444,7 +444,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
@@ -585,7 +585,7 @@ def __init__(
         Args:
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
         """
         from .sx import SXGate
 
@@ -785,7 +785,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
@@ -1029,7 +1029,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
@@ -1204,7 +1204,7 @@ def control(
             num_ctrl_qubits: number of control qubits.
             label: An optional label for the gate [Default: ``None``]
             ctrl_state: control state expressed as integer,
-                string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
+                string (e.g. ``'110'``), or ``None``. If ``None``, use all 1s.
             annotated: indicates whether the controlled gate can be implemented
                 as an annotated gate.
 
diff --git a/qiskit/providers/basic_provider/basic_provider_tools.py b/qiskit/providers/basic_provider/basic_provider_tools.py
index 030c629275ed..b2670cc0977f 100644
--- a/qiskit/providers/basic_provider/basic_provider_tools.py
+++ b/qiskit/providers/basic_provider/basic_provider_tools.py
@@ -23,7 +23,30 @@
 from qiskit.exceptions import QiskitError
 
 # Single qubit gates supported by ``single_gate_params``.
-SINGLE_QUBIT_GATES = ("U", "u", "h", "p", "u1", "u2", "u3", "rz", "sx", "x")
+SINGLE_QUBIT_GATES = {
+    "U": gates.UGate,
+    "u": gates.UGate,
+    "u1": gates.U1Gate,
+    "u2": gates.U2Gate,
+    "u3": gates.U3Gate,
+    "h": gates.HGate,
+    "p": gates.PhaseGate,
+    "s": gates.SGate,
+    "sdg": gates.SdgGate,
+    "sx": gates.SXGate,
+    "sxdg": gates.SXdgGate,
+    "t": gates.TGate,
+    "tdg": gates.TdgGate,
+    "x": gates.XGate,
+    "y": gates.YGate,
+    "z": gates.ZGate,
+    "id": gates.IGate,
+    "i": gates.IGate,
+    "r": gates.RGate,
+    "rx": gates.RXGate,
+    "ry": gates.RYGate,
+    "rz": gates.RZGate,
+}
 
 
 def single_gate_matrix(gate: str, params: list[float] | None = None) -> np.ndarray:
@@ -40,42 +63,55 @@ def single_gate_matrix(gate: str, params: list[float] | None = None) -> np.ndarr
     """
     if params is None:
         params = []
-
-    if gate == "U":
-        gc = gates.UGate
-    elif gate == "u3":
-        gc = gates.U3Gate
-    elif gate == "h":
-        gc = gates.HGate
-    elif gate == "u":
-        gc = gates.UGate
-    elif gate == "p":
-        gc = gates.PhaseGate
-    elif gate == "u2":
-        gc = gates.U2Gate
-    elif gate == "u1":
-        gc = gates.U1Gate
-    elif gate == "rz":
-        gc = gates.RZGate
-    elif gate == "id":
-        gc = gates.IGate
-    elif gate == "sx":
-        gc = gates.SXGate
-    elif gate == "x":
-        gc = gates.XGate
+    if gate in SINGLE_QUBIT_GATES:
+        gc = SINGLE_QUBIT_GATES[gate]
     else:
         raise QiskitError("Gate is not a valid basis gate for this simulator: %s" % gate)
 
     return gc(*params).to_matrix()
 
 
-# Cache CX matrix as no parameters.
-_CX_MATRIX = gates.CXGate().to_matrix()
-
-
-def cx_gate_matrix() -> np.ndarray:
-    """Get the matrix for a controlled-NOT gate."""
-    return _CX_MATRIX
+# Two qubit gates WITHOUT parameters: name -> matrix
+TWO_QUBIT_GATES = {
+    "CX": gates.CXGate().to_matrix(),
+    "cx": gates.CXGate().to_matrix(),
+    "ecr": gates.ECRGate().to_matrix(),
+    "cy": gates.CYGate().to_matrix(),
+    "cz": gates.CZGate().to_matrix(),
+    "swap": gates.SwapGate().to_matrix(),
+    "iswap": gates.iSwapGate().to_matrix(),
+    "ch": gates.CHGate().to_matrix(),
+    "cs": gates.CSGate().to_matrix(),
+    "csdg": gates.CSdgGate().to_matrix(),
+    "csx": gates.CSXGate().to_matrix(),
+    "dcx": gates.DCXGate().to_matrix(),
+}
+
+# Two qubit gates WITH parameters: name -> class
+TWO_QUBIT_GATES_WITH_PARAMETERS = {
+    "cp": gates.CPhaseGate,
+    "crx": gates.CRXGate,
+    "cry": gates.CRYGate,
+    "crz": gates.CRZGate,
+    "cu": gates.CUGate,
+    "cu1": gates.CU1Gate,
+    "cu3": gates.CU3Gate,
+    "rxx": gates.RXXGate,
+    "ryy": gates.RYYGate,
+    "rzz": gates.RZZGate,
+    "rzx": gates.RZXGate,
+    "xx_minus_yy": gates.XXMinusYYGate,
+    "xx_plus_yy": gates.XXPlusYYGate,
+}
+
+
+# Three qubit gates: name -> matrix
+THREE_QUBIT_GATES = {
+    "ccx": gates.CCXGate().to_matrix(),
+    "ccz": gates.CCZGate().to_matrix(),
+    "rccx": gates.RCCXGate().to_matrix(),
+    "cswap": gates.CSwapGate().to_matrix(),
+}
 
 
 def einsum_matmul_index(gate_indices: list[int], number_of_qubits: int) -> str:
diff --git a/qiskit/providers/basic_provider/basic_simulator.py b/qiskit/providers/basic_provider/basic_simulator.py
index e19021519194..b03a8df7ae5a 100644
--- a/qiskit/providers/basic_provider/basic_simulator.py
+++ b/qiskit/providers/basic_provider/basic_simulator.py
@@ -40,7 +40,7 @@
 
 from qiskit.circuit import QuantumCircuit
 from qiskit.circuit.library import UnitaryGate
-from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
+from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping, GlobalPhaseGate
 from qiskit.providers import Provider
 from qiskit.providers.backend import BackendV2
 from qiskit.providers.models import BackendConfiguration
@@ -51,8 +51,12 @@
 
 from .basic_provider_job import BasicProviderJob
 from .basic_provider_tools import single_gate_matrix
-from .basic_provider_tools import SINGLE_QUBIT_GATES
-from .basic_provider_tools import cx_gate_matrix
+from .basic_provider_tools import (
+    SINGLE_QUBIT_GATES,
+    TWO_QUBIT_GATES,
+    TWO_QUBIT_GATES_WITH_PARAMETERS,
+    THREE_QUBIT_GATES,
+)
 from .basic_provider_tools import einsum_vecmul_index
 from .exceptions import BasicProviderError
 
@@ -138,21 +142,59 @@ def _build_basic_target(self) -> Target:
             num_qubits=None,
         )
         basis_gates = [
+            "ccx",
+            "ccz",
+            "ch",
+            "cp",
+            "crx",
+            "cry",
+            "crz",
+            "cs",
+            "csdg",
+            "cswap",
+            "csx",
+            "cu",
+            "cu1",
+            "cu3",
+            "cx",
+            "cy",
+            "cz",
+            "dcx",
+            "delay",
+            "ecr",
+            "global_phase",
             "h",
-            "u",
+            "id",
+            "iswap",
+            "measure",
             "p",
+            "r",
+            "rccx",
+            "reset",
+            "rx",
+            "rxx",
+            "ry",
+            "ryy",
+            "rz",
+            "rzx",
+            "rzz",
+            "s",
+            "sdg",
+            "swap",
+            "sx",
+            "sxdg",
+            "t",
+            "tdg",
+            "u",
             "u1",
             "u2",
             "u3",
-            "rz",
-            "sx",
-            "x",
-            "cx",
-            "id",
             "unitary",
-            "measure",
-            "delay",
-            "reset",
+            "x",
+            "xx_minus_yy",
+            "xx_plus_yy",
+            "y",
+            "z",
         ]
         inst_mapping = get_standard_gate_name_mapping()
         for name in basis_gates:
@@ -617,24 +659,41 @@ def run_experiment(self, experiment: QasmQobjExperiment) -> dict[str, ...]:
                             value >>= 1
                         if value != int(operation.conditional.val, 16):
                             continue
-                # Check if single  gate
                 if operation.name == "unitary":
                     qubits = operation.qubits
                     gate = operation.params[0]
                     self._add_unitary(gate, qubits)
+                elif operation.name in ("id", "u0", "delay"):
+                    pass
+                elif operation.name == "global_phase":
+                    params = getattr(operation, "params", None)
+                    gate = GlobalPhaseGate(*params).to_matrix()
+                    self._add_unitary(gate, [])
+                # Check if single qubit gate
                 elif operation.name in SINGLE_QUBIT_GATES:
                     params = getattr(operation, "params", None)
                     qubit = operation.qubits[0]
                     gate = single_gate_matrix(operation.name, params)
                     self._add_unitary(gate, [qubit])
-                # Check if CX gate
+                elif operation.name in TWO_QUBIT_GATES_WITH_PARAMETERS:
+                    params = getattr(operation, "params", None)
+                    qubit0 = operation.qubits[0]
+                    qubit1 = operation.qubits[1]
+                    gate = TWO_QUBIT_GATES_WITH_PARAMETERS[operation.name](*params).to_matrix()
+                    self._add_unitary(gate, [qubit0, qubit1])
                 elif operation.name in ("id", "u0"):
                     pass
-                elif operation.name in ("CX", "cx"):
+                elif operation.name in TWO_QUBIT_GATES:
                     qubit0 = operation.qubits[0]
                     qubit1 = operation.qubits[1]
-                    gate = cx_gate_matrix()
+                    gate = TWO_QUBIT_GATES[operation.name]
                     self._add_unitary(gate, [qubit0, qubit1])
+                elif operation.name in THREE_QUBIT_GATES:
+                    qubit0 = operation.qubits[0]
+                    qubit1 = operation.qubits[1]
+                    qubit2 = operation.qubits[2]
+                    gate = THREE_QUBIT_GATES[operation.name]
+                    self._add_unitary(gate, [qubit0, qubit1, qubit2])
                 # Check if reset
                 elif operation.name == "reset":
                     qubit = operation.qubits[0]
diff --git a/releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml b/releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml
new file mode 100644
index 000000000000..755403d98a32
--- /dev/null
+++ b/releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml
@@ -0,0 +1,5 @@
+---
+features_providers:
+  - |
+    The :class:`.BasicSimulator` python-based simulator included in :mod:`qiskit.providers.basic_provider`
+    now includes all the standard gates (:mod:`qiskit.circuit.library .standard_gates`) up to 3 qubits.
diff --git a/test/python/providers/basic_provider/test_standard_library.py b/test/python/providers/basic_provider/test_standard_library.py
new file mode 100644
index 000000000000..3d6b5c83ccc8
--- /dev/null
+++ b/test/python/providers/basic_provider/test_standard_library.py
@@ -0,0 +1,531 @@
+# 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=missing-function-docstring, missing-module-docstring
+
+import unittest
+
+from qiskit import QuantumCircuit
+from qiskit.providers.basic_provider import BasicSimulator
+import qiskit.circuit.library.standard_gates as lib
+from test import QiskitTestCase  # pylint: disable=wrong-import-order
+
+
+class TestStandardGates(QiskitTestCase):
+    """Standard gates support in BasicSimulator, up to 3 qubits"""
+
+    def setUp(self):
+        super().setUp()
+        self.seed = 43
+        self.shots = 1
+        self.circuit = QuantumCircuit(4)
+
+    def test_barrier(self):
+        self.circuit.barrier(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_barrier_none(self):
+        self.circuit.barrier()
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_unitary(self):
+        matrix = [[0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0], [0, 1, 0, 0]]
+        self.circuit.unitary(matrix, [0, 1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_u(self):
+        self.circuit.u(0.5, 1.5, 1.5, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_u1(self):
+        self.circuit.append(lib.U1Gate(0.5), [1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_u2(self):
+        self.circuit.append(lib.U2Gate(0.5, 0.5), [1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_u3(self):
+        self.circuit.append(lib.U3Gate(0.5, 0.5, 0.5), [1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ccx(self):
+        self.circuit.ccx(0, 1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ccz(self):
+        self.circuit.ccz(0, 1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ch(self):
+        self.circuit.ch(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cp(self):
+        self.circuit.cp(0, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_crx(self):
+        self.circuit.crx(1, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cry(self):
+        self.circuit.cry(1, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_crz(self):
+        self.circuit.crz(1, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cswap(self):
+        self.circuit.cswap(0, 1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cu1(self):
+        self.circuit.append(lib.CU1Gate(1), [1, 2])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cu3(self):
+        self.circuit.append(lib.CU3Gate(1, 2, 3), [1, 2])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cx(self):
+        self.circuit.cx(1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ecr(self):
+        self.circuit.ecr(1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cy(self):
+        self.circuit.cy(1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cz(self):
+        self.circuit.cz(1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_h(self):
+        self.circuit.h(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_id(self):
+        self.circuit.id(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rx(self):
+        self.circuit.rx(1, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ry(self):
+        self.circuit.ry(1, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rz(self):
+        self.circuit.rz(1, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rxx(self):
+        self.circuit.rxx(1, 1, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rzx(self):
+        self.circuit.rzx(1, 1, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_ryy(self):
+        self.circuit.ryy(1, 1, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rzz(self):
+        self.circuit.rzz(1, 1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_s(self):
+        self.circuit.s(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_sdg(self):
+        self.circuit.sdg(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_sx(self):
+        self.circuit.sx(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_sxdg(self):
+        self.circuit.sxdg(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_swap(self):
+        self.circuit.swap(1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_iswap(self):
+        self.circuit.iswap(1, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_p(self):
+        self.circuit.p(1, 0)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_r(self):
+        self.circuit.r(0.5, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_t(self):
+        self.circuit.t(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_tdg(self):
+        self.circuit.tdg(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_x(self):
+        self.circuit.x(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_y(self):
+        self.circuit.y(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_z(self):
+        self.circuit.z(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cs(self):
+        self.circuit.cs(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_csdg(self):
+        self.circuit.csdg(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_csx(self):
+        self.circuit.csx(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_cu(self):
+        self.circuit.cu(0.5, 0.5, 0.5, 0.5, 0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_dcx(self):
+        self.circuit.dcx(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_delay(self):
+        self.circuit.delay(0, 1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_reset(self):
+        self.circuit.reset(1)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_rcx(self):
+        self.circuit.rccx(0, 1, 2)
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_global_phase(self):
+        qc = self.circuit
+        qc.append(lib.GlobalPhaseGate(0.1), [])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_xx_minus_yy(self):
+        self.circuit.append(lib.XXMinusYYGate(0.1, 0.2), [0, 1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+    def test_xx_plus_yy(self):
+        self.circuit.append(lib.XXPlusYYGate(0.1, 0.2), [0, 1])
+        self.circuit.measure_all()
+        result = (
+            BasicSimulator().run(self.circuit, shots=self.shots, seed_simulator=self.seed).result()
+        )
+        self.assertEqual(result.success, True)
+
+
+class TestStandardGatesTarget(QiskitTestCase):
+    """Standard gates, up to 3 qubits, as a target"""
+
+    def test_target(self):
+        target = BasicSimulator().target
+        expected = {
+            "cz",
+            "u3",
+            "p",
+            "cswap",
+            "z",
+            "cu1",
+            "ecr",
+            "reset",
+            "ch",
+            "cy",
+            "dcx",
+            "crx",
+            "sx",
+            "unitary",
+            "csdg",
+            "rzz",
+            "measure",
+            "swap",
+            "csx",
+            "y",
+            "s",
+            "xx_plus_yy",
+            "cs",
+            "h",
+            "t",
+            "u",
+            "rxx",
+            "cu",
+            "rzx",
+            "ry",
+            "rx",
+            "cu3",
+            "tdg",
+            "u2",
+            "xx_minus_yy",
+            "global_phase",
+            "u1",
+            "id",
+            "cx",
+            "cp",
+            "rz",
+            "sxdg",
+            "x",
+            "ryy",
+            "sdg",
+            "ccz",
+            "delay",
+            "crz",
+            "iswap",
+            "ccx",
+            "cry",
+            "rccx",
+            "r",
+        }
+        self.assertEqual(set(target.operation_names), expected)
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py
index fe209e3571ae..01ec7ebf133a 100644
--- a/test/python/transpiler/test_passmanager_config.py
+++ b/test/python/transpiler/test_passmanager_config.py
@@ -93,39 +93,77 @@ def test_str(self):
         pm_config.inst_map = None
         str_out = str(pm_config)
         expected = """Pass Manager Config:
-	initial_layout: None
-	basis_gates: ['h', 'u', 'p', 'u1', 'u2', 'u3', 'rz', 'sx', 'x', 'cx', 'id', 'unitary', 'measure', 'delay', 'reset']
-	inst_map: None
-	coupling_map: None
-	layout_method: None
-	routing_method: None
-	translation_method: None
-	scheduling_method: None
-	instruction_durations: 
-	backend_properties: None
-	approximation_degree: None
-	seed_transpiler: None
-	timing_constraints: None
-	unitary_synthesis_method: default
-	unitary_synthesis_plugin_config: None
-	target: Target: Basic Target
-	Number of qubits: None
-	Instructions:
-		h
-		u
-		p
-		u1
-		u2
-		u3
-		rz
-		sx
-		x
-		cx
-		id
-		unitary
-		measure
-		delay
-		reset
-	
+\tinitial_layout: None
+\tbasis_gates: ['ccx', 'ccz', 'ch', 'cp', 'crx', 'cry', 'crz', 'cs', 'csdg', 'cswap', 'csx', 'cu', 'cu1', 'cu3', 'cx', 'cy', 'cz', 'dcx', 'delay', 'ecr', 'global_phase', 'h', 'id', 'iswap', 'measure', 'p', 'r', 'rccx', 'reset', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzx', 'rzz', 's', 'sdg', 'swap', 'sx', 'sxdg', 't', 'tdg', 'u', 'u1', 'u2', 'u3', 'unitary', 'x', 'xx_minus_yy', 'xx_plus_yy', 'y', 'z']
+\tinst_map: None
+\tcoupling_map: None
+\tlayout_method: None
+\trouting_method: None
+\ttranslation_method: None
+\tscheduling_method: None
+\tinstruction_durations:\u0020
+\tbackend_properties: None
+\tapproximation_degree: None
+\tseed_transpiler: None
+\ttiming_constraints: None
+\tunitary_synthesis_method: default
+\tunitary_synthesis_plugin_config: None
+\ttarget: Target: Basic Target
+\tNumber of qubits: None
+\tInstructions:
+\t\tccx
+\t\tccz
+\t\tch
+\t\tcp
+\t\tcrx
+\t\tcry
+\t\tcrz
+\t\tcs
+\t\tcsdg
+\t\tcswap
+\t\tcsx
+\t\tcu
+\t\tcu1
+\t\tcu3
+\t\tcx
+\t\tcy
+\t\tcz
+\t\tdcx
+\t\tdelay
+\t\tecr
+\t\tglobal_phase
+\t\th
+\t\tid
+\t\tiswap
+\t\tmeasure
+\t\tp
+\t\tr
+\t\trccx
+\t\treset
+\t\trx
+\t\trxx
+\t\try
+\t\tryy
+\t\trz
+\t\trzx
+\t\trzz
+\t\ts
+\t\tsdg
+\t\tswap
+\t\tsx
+\t\tsxdg
+\t\tt
+\t\ttdg
+\t\tu
+\t\tu1
+\t\tu2
+\t\tu3
+\t\tunitary
+\t\tx
+\t\txx_minus_yy
+\t\txx_plus_yy
+\t\ty
+\t\tz
+\t
 """
         self.assertEqual(str_out, expected)

From 9c22197c3e4fc2794fae977469cd19d86cab021e Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Tue, 30 Apr 2024 12:40:25 +0100
Subject: [PATCH 038/179] Support standalone `expr.Var` in QPY (#11651)

* Support standalone `expr.Var` in QPY

This necessitates adding some extra definitions to the circuit header.
When standalone variables are used, we do not re-encode the entire
variable (which would take a miniumum of ~20 bytes per variable), but
instead just emit an index into the order that the variables were
defined at the top of the circuit.

At present, each control-flow scope will store the `Var` nodes anew, so
it's not a perfect only-store-once system.  This is consistent with how
registers are handled, though, and the QPY format isn't particularly
memory optimised as things stand.

* Add backwards compatibility tests
---
 qiskit/qpy/__init__.py                        | 114 +++++++-
 qiskit/qpy/binary_io/circuits.py              | 245 +++++++++++++++---
 qiskit/qpy/binary_io/value.py                 | 182 +++++++++++--
 qiskit/qpy/common.py                          |   2 +-
 qiskit/qpy/exceptions.py                      |  20 ++
 qiskit/qpy/formats.py                         |  29 +++
 qiskit/qpy/type_keys.py                       |  20 ++
 .../circuit/test_circuit_load_from_qpy.py     | 170 +++++++++++-
 test/qpy_compat/test_qpy.py                   |  50 ++++
 9 files changed, 762 insertions(+), 70 deletions(-)

diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py
index fed09b8717a6..7851db5c2a10 100644
--- a/qiskit/qpy/__init__.py
+++ b/qiskit/qpy/__init__.py
@@ -79,6 +79,12 @@
 
 .. autoexception:: QpyError
 
+When a lower-than-maximum target QPY version is set for serialization, but the object to be
+serialized contains features that cannot be represented in that format, a subclass of
+:exc:`QpyError` is raised:
+
+.. autoexception:: UnsupportedFeatureForVersion
+
 Attributes:
     QPY_VERSION (int): The current QPY format version as of this release. This
         is the default value of the ``version`` keyword argument on
@@ -285,12 +291,97 @@
 The file header is immediately followed by the circuit payloads.
 Each individual circuit is composed of the following parts:
 
-``HEADER | METADATA | REGISTERS | CUSTOM_DEFINITIONS | INSTRUCTIONS``
+``HEADER | METADATA | REGISTERS | STANDALONE_VARS | CUSTOM_DEFINITIONS | INSTRUCTIONS``
+
+The ``STANDALONE_VARS`` are new in QPY version 12; before that, there was no data between
+``REGISTERS`` and ``CUSTOM_DEFINITIONS``.
 
 There is a circuit payload for each circuit (where the total number is dictated
 by ``num_circuits`` in the file header). There is no padding between the
 circuits in the data.
 
+.. _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:
+
+.. code-block:: c
+
+    struct {
+        uint16_t name_size;
+        char global_phase_type;
+        uint16_t global_phase_size;
+        uint32_t num_qubits;
+        uint32_t num_clbits;
+        uint64_t metadata_size;
+        uint32_t num_registers;
+        uint64_t num_instructions;
+        uint32_t num_vars;
+    } HEADER_V12;
+
+The ``HEADER_V12`` struct is followed immediately by the same name, global-phase, metadata
+and register information as the V2 version of the header.  Immediately following the registers is
+``num_vars`` instances of ``EXPR_VAR_STANDALONE`` that define the variables in this circuit.  After
+that, the data continues with custom definitions and instructions as in prior versions of QPY.
+
+
+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
+:class:`.ClassicalRegister`.  The payload is a C struct:
+
+.. code-block:: c
+
+    struct {
+        char uuid_bytes[16];
+        char usage;
+        uint16_t name_size;
+    }
+
+which is immediately followed by an ``EXPR_TYPE`` payload and then ``name_size`` bytes of UTF-8
+encoding string data containing the name of the variable.
+
+The ``char`` usage type code takes the following values:
+
+=========  =========================================================================================
+Type code  Meaning
+=========  =========================================================================================
+``I``      An ``input`` variable to the circuit.
+
+``C``      A ``capture`` variable to the circuit.
+
+``L``      A locally declared variable to the circuit.
+=========  =========================================================================================
+
+
+Changes to EXPR_VAR
+-------------------
+
+The EXPR_VAR variable has gained a new type code and payload, in addition to the pre-existing ones:
+
+===========================  =========  ============================================================
+Python class                 Type code  Payload
+===========================  =========  ============================================================
+:class:`.UUID`               ``U``      One ``uint32_t`` index of the variable into the series of
+                                        ``EXPR_VAR_STANDALONE`` variables that were written
+                                        immediately after the circuit header.
+===========================  =========  ============================================================
+
+Notably, this new type-code indexes into pre-defined variables from the circuit header, rather than
+redefining the variable again in each location it is used.
+
 .. _qpy_version_11:
 
 Version 11
@@ -337,17 +428,18 @@
 Version 10
 ==========
 
-Version 10 adds support for symengine-native serialization for objects of type
-:class:`~.ParameterExpression` as well as symbolic expressions in Pulse schedule blocks. Version
-10 also adds support for new fields in the :class:`~.TranspileLayout` class added in the Qiskit
-0.45.0 release.
+Version 10 adds support for:
+
+* symengine-native serialization for objects of type :class:`~.ParameterExpression` as well as
+  symbolic expressions in Pulse schedule blocks.
+* new fields in the :class:`~.TranspileLayout` class added in the Qiskit 0.45.0 release.
 
 The symbolic_encoding field is added to the file header, and a new encoding type char
 is introduced, mapped to each symbolic library as follows: ``p`` refers to sympy
 encoding and ``e`` refers to symengine encoding.
 
-FILE_HEADER
------------
+Changes to FILE_HEADER
+----------------------
 
 The contents of FILE_HEADER after V10 are defined as a C struct as:
 
@@ -360,10 +452,10 @@
         uint8_t qiskit_patch_version;
         uint64_t num_circuits;
         char symbolic_encoding;
-    }
+    } FILE_HEADER_V10;
 
-LAYOUT
-------
+Changes to LAYOUT
+-----------------
 
 The ``LAYOUT`` struct is updated to have an additional ``input_qubit_count`` field.
 With version 10 the ``LAYOUT`` struct is now:
@@ -1522,7 +1614,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom
 .. [#f3] https://docs.python.org/3/c-api/complex.html#c.Py_complex
 """
 
-from .exceptions import QpyError, QPYLoadingDeprecatedFeatureWarning
+from .exceptions import QpyError, UnsupportedFeatureForVersion, QPYLoadingDeprecatedFeatureWarning
 from .interface import dump, load
 
 # For backward compatibility. Provide, Runtime, Experiment call these private functions.
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
index 40bb5850043b..1cf003ff3585 100644
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -40,13 +40,39 @@
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.quantumregister import QuantumRegister, Qubit
-from qiskit.qpy import common, formats, type_keys
+from qiskit.qpy import common, formats, type_keys, exceptions
 from qiskit.qpy.binary_io import value, schedules
 from qiskit.quantum_info.operators import SparsePauliOp, Clifford
 from qiskit.synthesis import evolution as evo_synth
 from qiskit.transpiler.layout import Layout, TranspileLayout
 
 
+def _read_header_v12(file_obj, version, vectors, metadata_deserializer=None):
+    data = formats.CIRCUIT_HEADER_V12._make(
+        struct.unpack(
+            formats.CIRCUIT_HEADER_V12_PACK, file_obj.read(formats.CIRCUIT_HEADER_V12_SIZE)
+        )
+    )
+    name = file_obj.read(data.name_size).decode(common.ENCODE)
+    global_phase = value.loads_value(
+        data.global_phase_type,
+        file_obj.read(data.global_phase_size),
+        version=version,
+        vectors=vectors,
+    )
+    header = {
+        "global_phase": global_phase,
+        "num_qubits": data.num_qubits,
+        "num_clbits": data.num_clbits,
+        "num_registers": data.num_registers,
+        "num_instructions": data.num_instructions,
+        "num_vars": data.num_vars,
+    }
+    metadata_raw = file_obj.read(data.metadata_size)
+    metadata = json.loads(metadata_raw, cls=metadata_deserializer)
+    return header, name, metadata
+
+
 def _read_header_v2(file_obj, version, vectors, metadata_deserializer=None):
     data = formats.CIRCUIT_HEADER_V2._make(
         struct.unpack(
@@ -133,7 +159,14 @@ def _read_registers(file_obj, num_registers):
 
 
 def _loads_instruction_parameter(
-    type_key, data_bytes, version, vectors, registers, circuit, use_symengine
+    type_key,
+    data_bytes,
+    version,
+    vectors,
+    registers,
+    circuit,
+    use_symengine,
+    standalone_vars,
 ):
     if type_key == type_keys.Program.CIRCUIT:
         param = common.data_from_binary(data_bytes, read_circuit, version=version)
@@ -152,6 +185,7 @@ def _loads_instruction_parameter(
                 registers=registers,
                 circuit=circuit,
                 use_symengine=use_symengine,
+                standalone_vars=standalone_vars,
             )
         )
     elif type_key == type_keys.Value.INTEGER:
@@ -172,6 +206,7 @@ def _loads_instruction_parameter(
             clbits=clbits,
             cregs=registers["c"],
             use_symengine=use_symengine,
+            standalone_vars=standalone_vars,
         )
 
     return param
@@ -186,7 +221,14 @@ def _loads_register_param(data_bytes, circuit, registers):
 
 
 def _read_instruction(
-    file_obj, circuit, registers, custom_operations, version, vectors, use_symengine
+    file_obj,
+    circuit,
+    registers,
+    custom_operations,
+    version,
+    vectors,
+    use_symengine,
+    standalone_vars,
 ):
     if version < 5:
         instruction = formats.CIRCUIT_INSTRUCTION._make(
@@ -224,6 +266,7 @@ def _read_instruction(
             clbits=circuit.clbits,
             cregs=registers["c"],
             use_symengine=use_symengine,
+            standalone_vars=standalone_vars,
         )
     # Load Arguments
     if circuit is not None:
@@ -252,14 +295,28 @@ def _read_instruction(
     for _param in range(instruction.num_parameters):
         type_key, data_bytes = common.read_generic_typed_data(file_obj)
         param = _loads_instruction_parameter(
-            type_key, data_bytes, version, vectors, registers, circuit, use_symengine
+            type_key,
+            data_bytes,
+            version,
+            vectors,
+            registers,
+            circuit,
+            use_symengine,
+            standalone_vars,
         )
         params.append(param)
 
     # Load Gate object
     if gate_name in {"Gate", "Instruction", "ControlledGate"}:
         inst_obj = _parse_custom_operation(
-            custom_operations, gate_name, params, version, vectors, registers, use_symengine
+            custom_operations,
+            gate_name,
+            params,
+            version,
+            vectors,
+            registers,
+            use_symengine,
+            standalone_vars,
         )
         inst_obj.condition = condition
         if instruction.label_size > 0:
@@ -270,7 +327,14 @@ def _read_instruction(
         return None
     elif gate_name in custom_operations:
         inst_obj = _parse_custom_operation(
-            custom_operations, gate_name, params, version, vectors, registers, use_symengine
+            custom_operations,
+            gate_name,
+            params,
+            version,
+            vectors,
+            registers,
+            use_symengine,
+            standalone_vars,
         )
         inst_obj.condition = condition
         if instruction.label_size > 0:
@@ -361,7 +425,14 @@ def _read_instruction(
 
 
 def _parse_custom_operation(
-    custom_operations, gate_name, params, version, vectors, registers, use_symengine
+    custom_operations,
+    gate_name,
+    params,
+    version,
+    vectors,
+    registers,
+    use_symengine,
+    standalone_vars,
 ):
     if version >= 5:
         (
@@ -394,7 +465,14 @@ def _parse_custom_operation(
     if version >= 5 and type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
         with io.BytesIO(base_gate_raw) as base_gate_obj:
             base_gate = _read_instruction(
-                base_gate_obj, None, registers, custom_operations, version, vectors, use_symengine
+                base_gate_obj,
+                None,
+                registers,
+                custom_operations,
+                version,
+                vectors,
+                use_symengine,
+                standalone_vars,
             )
         if ctrl_state < 2**num_ctrl_qubits - 1:
             # If open controls, we need to discard the control suffix when setting the name.
@@ -413,7 +491,14 @@ def _parse_custom_operation(
     if version >= 11 and type_key == type_keys.CircuitInstruction.ANNOTATED_OPERATION:
         with io.BytesIO(base_gate_raw) as base_gate_obj:
             base_gate = _read_instruction(
-                base_gate_obj, None, registers, custom_operations, version, vectors, use_symengine
+                base_gate_obj,
+                None,
+                registers,
+                custom_operations,
+                version,
+                vectors,
+                use_symengine,
+                standalone_vars,
             )
         inst_obj = AnnotatedOperation(base_op=base_gate, modifiers=params)
         return inst_obj
@@ -572,10 +657,12 @@ def _dumps_register(register, index_map):
     return b"\x00" + str(index_map["c"][register]).encode(common.ENCODE)
 
 
-def _dumps_instruction_parameter(param, index_map, use_symengine):
+def _dumps_instruction_parameter(
+    param, index_map, use_symengine, *, version, standalone_var_indices
+):
     if isinstance(param, QuantumCircuit):
         type_key = type_keys.Program.CIRCUIT
-        data_bytes = common.data_to_binary(param, write_circuit)
+        data_bytes = common.data_to_binary(param, write_circuit, version=version)
     elif isinstance(param, Modifier):
         type_key = type_keys.Value.MODIFIER
         data_bytes = common.data_to_binary(param, _write_modifier)
@@ -585,7 +672,12 @@ def _dumps_instruction_parameter(param, index_map, use_symengine):
     elif isinstance(param, tuple):
         type_key = type_keys.Container.TUPLE
         data_bytes = common.sequence_to_binary(
-            param, _dumps_instruction_parameter, index_map=index_map, use_symengine=use_symengine
+            param,
+            _dumps_instruction_parameter,
+            index_map=index_map,
+            use_symengine=use_symengine,
+            version=version,
+            standalone_var_indices=standalone_var_indices,
         )
     elif isinstance(param, int):
         # TODO This uses little endian. This should be fixed in next QPY version.
@@ -600,14 +692,25 @@ def _dumps_instruction_parameter(param, index_map, use_symengine):
         data_bytes = _dumps_register(param, index_map)
     else:
         type_key, data_bytes = value.dumps_value(
-            param, index_map=index_map, use_symengine=use_symengine
+            param,
+            index_map=index_map,
+            use_symengine=use_symengine,
+            standalone_var_indices=standalone_var_indices,
         )
 
     return type_key, data_bytes
 
 
 # pylint: disable=too-many-boolean-expressions
-def _write_instruction(file_obj, instruction, custom_operations, index_map, use_symengine, version):
+def _write_instruction(
+    file_obj,
+    instruction,
+    custom_operations,
+    index_map,
+    use_symengine,
+    version,
+    standalone_var_indices=None,
+):
     if isinstance(instruction.operation, Instruction):
         gate_class_name = instruction.operation.base_class.__name__
     else:
@@ -702,7 +805,12 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_
     file_obj.write(gate_class_name)
     file_obj.write(label_raw)
     if condition_type is type_keys.Condition.EXPRESSION:
-        value.write_value(file_obj, op_condition, index_map=index_map)
+        value.write_value(
+            file_obj,
+            op_condition,
+            index_map=index_map,
+            standalone_var_indices=standalone_var_indices,
+        )
     else:
         file_obj.write(condition_register)
     # Encode instruction args
@@ -718,7 +826,13 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_
         file_obj.write(instruction_arg_raw)
     # Encode instruction params
     for param in instruction_params:
-        type_key, data_bytes = _dumps_instruction_parameter(param, index_map, use_symengine)
+        type_key, data_bytes = _dumps_instruction_parameter(
+            param,
+            index_map,
+            use_symengine,
+            version=version,
+            standalone_var_indices=standalone_var_indices,
+        )
         common.write_generic_typed_data(file_obj, type_key, data_bytes)
     return custom_operations_list
 
@@ -788,7 +902,9 @@ def _write_modifier(file_obj, modifier):
     file_obj.write(modifier_data)
 
 
-def _write_custom_operation(file_obj, name, operation, custom_operations, use_symengine, version):
+def _write_custom_operation(
+    file_obj, name, operation, custom_operations, use_symengine, version, *, standalone_var_indices
+):
     type_key = type_keys.CircuitInstruction.assign(operation)
     has_definition = False
     size = 0
@@ -813,7 +929,7 @@ def _write_custom_operation(file_obj, name, operation, custom_operations, use_sy
         # Build internal definition to support overloaded subclasses by
         # calling definition getter on object
         operation.definition  # pylint: disable=pointless-statement
-        data = common.data_to_binary(operation._definition, write_circuit)
+        data = common.data_to_binary(operation._definition, write_circuit, version=version)
         size = len(data)
         num_ctrl_qubits = operation.num_ctrl_qubits
         ctrl_state = operation.ctrl_state
@@ -823,7 +939,7 @@ def _write_custom_operation(file_obj, name, operation, custom_operations, use_sy
         base_gate = operation.base_op
     elif operation.definition is not None:
         has_definition = True
-        data = common.data_to_binary(operation.definition, write_circuit)
+        data = common.data_to_binary(operation.definition, write_circuit, version=version)
         size = len(data)
     if base_gate is None:
         base_gate_raw = b""
@@ -836,6 +952,7 @@ def _write_custom_operation(file_obj, name, operation, custom_operations, use_sy
                 {},
                 use_symengine,
                 version,
+                standalone_var_indices=standalone_var_indices,
             )
             base_gate_raw = base_gate_buffer.getvalue()
     name_raw = name.encode(common.ENCODE)
@@ -1103,23 +1220,49 @@ def write_circuit(
     num_registers = num_qregs + num_cregs
 
     # Write circuit header
-    header_raw = formats.CIRCUIT_HEADER_V2(
-        name_size=len(circuit_name),
-        global_phase_type=global_phase_type,
-        global_phase_size=len(global_phase_data),
-        num_qubits=circuit.num_qubits,
-        num_clbits=circuit.num_clbits,
-        metadata_size=metadata_size,
-        num_registers=num_registers,
-        num_instructions=num_instructions,
-    )
-    header = struct.pack(formats.CIRCUIT_HEADER_V2_PACK, *header_raw)
-    file_obj.write(header)
-    file_obj.write(circuit_name)
-    file_obj.write(global_phase_data)
-    file_obj.write(metadata_raw)
-    # Write header payload
-    file_obj.write(registers_raw)
+    if version >= 12:
+        header_raw = formats.CIRCUIT_HEADER_V12(
+            name_size=len(circuit_name),
+            global_phase_type=global_phase_type,
+            global_phase_size=len(global_phase_data),
+            num_qubits=circuit.num_qubits,
+            num_clbits=circuit.num_clbits,
+            metadata_size=metadata_size,
+            num_registers=num_registers,
+            num_instructions=num_instructions,
+            num_vars=circuit.num_vars,
+        )
+        header = struct.pack(formats.CIRCUIT_HEADER_V12_PACK, *header_raw)
+        file_obj.write(header)
+        file_obj.write(circuit_name)
+        file_obj.write(global_phase_data)
+        file_obj.write(metadata_raw)
+        # Write header payload
+        file_obj.write(registers_raw)
+        standalone_var_indices = value.write_standalone_vars(file_obj, circuit)
+    else:
+        if circuit.num_vars:
+            raise exceptions.UnsupportedFeatureForVersion(
+                "circuits containing realtime variables", required=12, target=version
+            )
+        header_raw = formats.CIRCUIT_HEADER_V2(
+            name_size=len(circuit_name),
+            global_phase_type=global_phase_type,
+            global_phase_size=len(global_phase_data),
+            num_qubits=circuit.num_qubits,
+            num_clbits=circuit.num_clbits,
+            metadata_size=metadata_size,
+            num_registers=num_registers,
+            num_instructions=num_instructions,
+        )
+        header = struct.pack(formats.CIRCUIT_HEADER_V2_PACK, *header_raw)
+        file_obj.write(header)
+        file_obj.write(circuit_name)
+        file_obj.write(global_phase_data)
+        file_obj.write(metadata_raw)
+        file_obj.write(registers_raw)
+        standalone_var_indices = {}
+
     instruction_buffer = io.BytesIO()
     custom_operations = {}
     index_map = {}
@@ -1127,7 +1270,13 @@ def write_circuit(
     index_map["c"] = {bit: index for index, bit in enumerate(circuit.clbits)}
     for instruction in circuit.data:
         _write_instruction(
-            instruction_buffer, instruction, custom_operations, index_map, use_symengine, version
+            instruction_buffer,
+            instruction,
+            custom_operations,
+            index_map,
+            use_symengine,
+            version,
+            standalone_var_indices=standalone_var_indices,
         )
 
     with io.BytesIO() as custom_operations_buffer:
@@ -1145,6 +1294,7 @@ def write_circuit(
                         custom_operations,
                         use_symengine,
                         version,
+                        standalone_var_indices=standalone_var_indices,
                     )
                 )
 
@@ -1186,16 +1336,21 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
     vectors = {}
     if version < 2:
         header, name, metadata = _read_header(file_obj, metadata_deserializer=metadata_deserializer)
-    else:
+    elif version < 12:
         header, name, metadata = _read_header_v2(
             file_obj, version, vectors, metadata_deserializer=metadata_deserializer
         )
+    else:
+        header, name, metadata = _read_header_v12(
+            file_obj, version, vectors, metadata_deserializer=metadata_deserializer
+        )
 
     global_phase = header["global_phase"]
     num_qubits = header["num_qubits"]
     num_clbits = header["num_clbits"]
     num_registers = header["num_registers"]
     num_instructions = header["num_instructions"]
+    num_vars = header.get("num_vars", 0)
     # `out_registers` is two "name: register" maps segregated by type for the rest of QPY, and
     # `all_registers` is the complete ordered list used to construct the `QuantumCircuit`.
     out_registers = {"q": {}, "c": {}}
@@ -1252,6 +1407,7 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
             "q": [Qubit() for _ in out_bits["q"]],
             "c": [Clbit() for _ in out_bits["c"]],
         }
+    var_segments, standalone_var_indices = value.read_standalone_vars(file_obj, num_vars)
     circ = QuantumCircuit(
         out_bits["q"],
         out_bits["c"],
@@ -1259,11 +1415,22 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
         name=name,
         global_phase=global_phase,
         metadata=metadata,
+        inputs=var_segments[type_keys.ExprVarDeclaration.INPUT],
+        captures=var_segments[type_keys.ExprVarDeclaration.CAPTURE],
     )
+    for declaration in var_segments[type_keys.ExprVarDeclaration.LOCAL]:
+        circ.add_uninitialized_var(declaration)
     custom_operations = _read_custom_operations(file_obj, version, vectors)
     for _instruction in range(num_instructions):
         _read_instruction(
-            file_obj, circ, out_registers, custom_operations, version, vectors, use_symengine
+            file_obj,
+            circ,
+            out_registers,
+            custom_operations,
+            version,
+            vectors,
+            use_symengine,
+            standalone_var_indices,
         )
 
     # Read calibrations
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
index 1c11d4ad27c1..a3d7ff088139 100644
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -95,11 +95,12 @@ def _write_parameter_expression(file_obj, obj, use_symengine):
 
 
 class _ExprWriter(expr.ExprVisitor[None]):
-    __slots__ = ("file_obj", "clbit_indices")
+    __slots__ = ("file_obj", "clbit_indices", "standalone_var_indices")
 
-    def __init__(self, file_obj, clbit_indices):
+    def __init__(self, file_obj, clbit_indices, standalone_var_indices):
         self.file_obj = file_obj
         self.clbit_indices = clbit_indices
+        self.standalone_var_indices = standalone_var_indices
 
     def visit_generic(self, node, /):
         raise exceptions.QpyError(f"unhandled Expr object '{node}'")
@@ -107,7 +108,15 @@ def visit_generic(self, node, /):
     def visit_var(self, node, /):
         self.file_obj.write(type_keys.Expression.VAR)
         _write_expr_type(self.file_obj, node.type)
-        if isinstance(node.var, Clbit):
+        if node.standalone:
+            self.file_obj.write(type_keys.ExprVar.UUID)
+            self.file_obj.write(
+                struct.pack(
+                    formats.EXPR_VAR_UUID_PACK,
+                    *formats.EXPR_VAR_UUID(self.standalone_var_indices[node]),
+                )
+            )
+        elif isinstance(node.var, Clbit):
             self.file_obj.write(type_keys.ExprVar.CLBIT)
             self.file_obj.write(
                 struct.pack(
@@ -178,8 +187,13 @@ def visit_binary(self, node, /):
         node.right.accept(self)
 
 
-def _write_expr(file_obj, node: expr.Expr, clbit_indices: collections.abc.Mapping[Clbit, int]):
-    node.accept(_ExprWriter(file_obj, clbit_indices))
+def _write_expr(
+    file_obj,
+    node: expr.Expr,
+    clbit_indices: collections.abc.Mapping[Clbit, int],
+    standalone_var_indices: collections.abc.Mapping[expr.Var, int],
+):
+    node.accept(_ExprWriter(file_obj, clbit_indices, standalone_var_indices))
 
 
 def _write_expr_type(file_obj, type_: types.Type):
@@ -315,12 +329,18 @@ def _read_expr(
     file_obj,
     clbits: collections.abc.Sequence[Clbit],
     cregs: collections.abc.Mapping[str, ClassicalRegister],
+    standalone_vars: collections.abc.Sequence[expr.Var],
 ) -> expr.Expr:
     # pylint: disable=too-many-return-statements
     type_key = file_obj.read(formats.EXPRESSION_DISCRIMINATOR_SIZE)
     type_ = _read_expr_type(file_obj)
     if type_key == type_keys.Expression.VAR:
         var_type_key = file_obj.read(formats.EXPR_VAR_DISCRIMINATOR_SIZE)
+        if var_type_key == type_keys.ExprVar.UUID:
+            payload = formats.EXPR_VAR_UUID._make(
+                struct.unpack(formats.EXPR_VAR_UUID_PACK, file_obj.read(formats.EXPR_VAR_UUID_SIZE))
+            )
+            return standalone_vars[payload.var_index]
         if var_type_key == type_keys.ExprVar.CLBIT:
             payload = formats.EXPR_VAR_CLBIT._make(
                 struct.unpack(
@@ -360,14 +380,20 @@ def _read_expr(
         payload = formats.EXPRESSION_CAST._make(
             struct.unpack(formats.EXPRESSION_CAST_PACK, file_obj.read(formats.EXPRESSION_CAST_SIZE))
         )
-        return expr.Cast(_read_expr(file_obj, clbits, cregs), type_, implicit=payload.implicit)
+        return expr.Cast(
+            _read_expr(file_obj, clbits, cregs, standalone_vars), type_, implicit=payload.implicit
+        )
     if type_key == type_keys.Expression.UNARY:
         payload = formats.EXPRESSION_UNARY._make(
             struct.unpack(
                 formats.EXPRESSION_UNARY_PACK, file_obj.read(formats.EXPRESSION_UNARY_SIZE)
             )
         )
-        return expr.Unary(expr.Unary.Op(payload.opcode), _read_expr(file_obj, clbits, cregs), type_)
+        return expr.Unary(
+            expr.Unary.Op(payload.opcode),
+            _read_expr(file_obj, clbits, cregs, standalone_vars),
+            type_,
+        )
     if type_key == type_keys.Expression.BINARY:
         payload = formats.EXPRESSION_BINARY._make(
             struct.unpack(
@@ -376,8 +402,8 @@ def _read_expr(
         )
         return expr.Binary(
             expr.Binary.Op(payload.opcode),
-            _read_expr(file_obj, clbits, cregs),
-            _read_expr(file_obj, clbits, cregs),
+            _read_expr(file_obj, clbits, cregs, standalone_vars),
+            _read_expr(file_obj, clbits, cregs, standalone_vars),
             type_,
         )
     raise exceptions.QpyError("Invalid classical-expression Expr key '{type_key}'")
@@ -395,7 +421,80 @@ def _read_expr_type(file_obj) -> types.Type:
     raise exceptions.QpyError(f"Invalid classical-expression Type key '{type_key}'")
 
 
-def dumps_value(obj, *, index_map=None, use_symengine=False):
+def read_standalone_vars(file_obj, num_vars):
+    """Read the ``num_vars`` standalone variable declarations from the file.
+
+    Args:
+        file_obj (File): a file-like object to read from.
+        num_vars (int): the number of variables to read.
+
+    Returns:
+        tuple[dict, list]: the first item is a mapping of the ``ExprVarDeclaration`` type keys to
+        the variables defined by that type key, and the second is the total order of variable
+        declarations.
+    """
+    read_vars = {
+        type_keys.ExprVarDeclaration.INPUT: [],
+        type_keys.ExprVarDeclaration.CAPTURE: [],
+        type_keys.ExprVarDeclaration.LOCAL: [],
+    }
+    var_order = []
+    for _ in range(num_vars):
+        data = formats.EXPR_VAR_DECLARATION._make(
+            struct.unpack(
+                formats.EXPR_VAR_DECLARATION_PACK,
+                file_obj.read(formats.EXPR_VAR_DECLARATION_SIZE),
+            )
+        )
+        type_ = _read_expr_type(file_obj)
+        name = file_obj.read(data.name_size).decode(common.ENCODE)
+        var = expr.Var(uuid.UUID(bytes=data.uuid_bytes), type_, name=name)
+        read_vars[data.usage].append(var)
+        var_order.append(var)
+    return read_vars, var_order
+
+
+def _write_standalone_var(file_obj, var, type_key):
+    name = var.name.encode(common.ENCODE)
+    file_obj.write(
+        struct.pack(
+            formats.EXPR_VAR_DECLARATION_PACK,
+            *formats.EXPR_VAR_DECLARATION(var.var.bytes, type_key, len(name)),
+        )
+    )
+    _write_expr_type(file_obj, var.type)
+    file_obj.write(name)
+
+
+def write_standalone_vars(file_obj, circuit):
+    """Write the standalone variables out from a circuit.
+
+    Args:
+        file_obj (File): the file-like object to write to.
+        circuit (QuantumCircuit): the circuit to take the variables from.
+
+    Returns:
+        dict[expr.Var, int]: a mapping of the variables written to the index that they were written
+        at.
+    """
+    index = 0
+    out = {}
+    for var in circuit.iter_input_vars():
+        _write_standalone_var(file_obj, var, type_keys.ExprVarDeclaration.INPUT)
+        out[var] = index
+        index += 1
+    for var in circuit.iter_captured_vars():
+        _write_standalone_var(file_obj, var, type_keys.ExprVarDeclaration.CAPTURE)
+        out[var] = index
+        index += 1
+    for var in circuit.iter_declared_vars():
+        _write_standalone_var(file_obj, var, type_keys.ExprVarDeclaration.LOCAL)
+        out[var] = index
+        index += 1
+    return out
+
+
+def dumps_value(obj, *, index_map=None, use_symengine=False, standalone_var_indices=None):
     """Serialize input value object.
 
     Args:
@@ -407,6 +506,8 @@ def dumps_value(obj, *, index_map=None, use_symengine=False):
             native mechanism. This is a faster serialization alternative, but not supported in all
             platforms. Please check that your target platform is supported by the symengine library
             before setting this option, as it will be required by qpy to deserialize the payload.
+        standalone_var_indices (dict): Dictionary that maps standalone :class:`.expr.Var` entries to
+            the index that should be used to refer to them.
 
     Returns:
         tuple: TypeKey and binary data.
@@ -438,14 +539,20 @@ def dumps_value(obj, *, index_map=None, use_symengine=False):
         )
     elif type_key == type_keys.Value.EXPRESSION:
         clbit_indices = {} if index_map is None else index_map["c"]
-        binary_data = common.data_to_binary(obj, _write_expr, clbit_indices=clbit_indices)
+        standalone_var_indices = {} if standalone_var_indices is None else standalone_var_indices
+        binary_data = common.data_to_binary(
+            obj,
+            _write_expr,
+            clbit_indices=clbit_indices,
+            standalone_var_indices=standalone_var_indices,
+        )
     else:
         raise exceptions.QpyError(f"Serialization for {type_key} is not implemented in value I/O.")
 
     return type_key, binary_data
 
 
-def write_value(file_obj, obj, *, index_map=None, use_symengine=False):
+def write_value(file_obj, obj, *, index_map=None, use_symengine=False, standalone_var_indices=None):
     """Write a value to the file like object.
 
     Args:
@@ -458,13 +565,28 @@ def write_value(file_obj, obj, *, index_map=None, use_symengine=False):
             native mechanism. This is a faster serialization alternative, but not supported in all
             platforms. Please check that your target platform is supported by the symengine library
             before setting this option, as it will be required by qpy to deserialize the payload.
+        standalone_var_indices (dict): Dictionary that maps standalone :class:`.expr.Var` entries to
+            the index that should be used to refer to them.
     """
-    type_key, data = dumps_value(obj, index_map=index_map, use_symengine=use_symengine)
+    type_key, data = dumps_value(
+        obj,
+        index_map=index_map,
+        use_symengine=use_symengine,
+        standalone_var_indices=standalone_var_indices,
+    )
     common.write_generic_typed_data(file_obj, type_key, data)
 
 
 def loads_value(
-    type_key, binary_data, version, vectors, *, clbits=(), cregs=None, use_symengine=False
+    type_key,
+    binary_data,
+    version,
+    vectors,
+    *,
+    clbits=(),
+    cregs=None,
+    use_symengine=False,
+    standalone_vars=(),
 ):
     """Deserialize input binary data to value object.
 
@@ -479,6 +601,8 @@ def loads_value(
             native mechanism. This is a faster serialization alternative, but not supported in all
             platforms. Please check that your target platform is supported by the symengine library
             before setting this option, as it will be required by qpy to deserialize the payload.
+        standalone_vars (Sequence[Var]): standalone :class:`.expr.Var` nodes in the order that they
+            were declared by the circuit header.
 
     Returns:
         any: Deserialized value object.
@@ -520,12 +644,27 @@ def loads_value(
                 use_symengine=use_symengine,
             )
     if type_key == type_keys.Value.EXPRESSION:
-        return common.data_from_binary(binary_data, _read_expr, clbits=clbits, cregs=cregs or {})
+        return common.data_from_binary(
+            binary_data,
+            _read_expr,
+            clbits=clbits,
+            cregs=cregs or {},
+            standalone_vars=standalone_vars,
+        )
 
     raise exceptions.QpyError(f"Serialization for {type_key} is not implemented in value I/O.")
 
 
-def read_value(file_obj, version, vectors, *, clbits=(), cregs=None, use_symengine=False):
+def read_value(
+    file_obj,
+    version,
+    vectors,
+    *,
+    clbits=(),
+    cregs=None,
+    use_symengine=False,
+    standalone_vars=(),
+):
     """Read a value from the file like object.
 
     Args:
@@ -538,6 +677,8 @@ def read_value(file_obj, version, vectors, *, clbits=(), cregs=None, use_symengi
             native mechanism. This is a faster serialization alternative, but not supported in all
             platforms. Please check that your target platform is supported by the symengine library
             before setting this option, as it will be required by qpy to deserialize the payload.
+        standalone_vars (Sequence[expr.Var]): standalone variables in the order they were defined in
+            the QPY payload.
 
     Returns:
         any: Deserialized value object.
@@ -545,5 +686,12 @@ def read_value(file_obj, version, vectors, *, clbits=(), cregs=None, use_symengi
     type_key, data = common.read_generic_typed_data(file_obj)
 
     return loads_value(
-        type_key, data, version, vectors, clbits=clbits, cregs=cregs, use_symengine=use_symengine
+        type_key,
+        data,
+        version,
+        vectors,
+        clbits=clbits,
+        cregs=cregs,
+        use_symengine=use_symengine,
+        standalone_vars=standalone_vars,
     )
diff --git a/qiskit/qpy/common.py b/qiskit/qpy/common.py
index 7cc11fb7ca05..048320d5cad6 100644
--- a/qiskit/qpy/common.py
+++ b/qiskit/qpy/common.py
@@ -20,7 +20,7 @@
 
 from qiskit.qpy import formats
 
-QPY_VERSION = 11
+QPY_VERSION = 12
 QPY_COMPATIBILITY_VERSION = 10
 ENCODE = "utf8"
 
diff --git a/qiskit/qpy/exceptions.py b/qiskit/qpy/exceptions.py
index c6cdb4303a62..5662e6029373 100644
--- a/qiskit/qpy/exceptions.py
+++ b/qiskit/qpy/exceptions.py
@@ -28,6 +28,26 @@ def __str__(self):
         return repr(self.message)
 
 
+class UnsupportedFeatureForVersion(QpyError):
+    """QPY error raised when the target dump version is too low for a feature that is present in the
+    object to be serialized."""
+
+    def __init__(self, feature: str, required: int, target: int):
+        """
+        Args:
+            feature: a description of the problematic feature.
+            required: the minimum version of QPY that would be required to represent this
+                feature.
+            target: the version of QPY that is being used in the serialization.
+        """
+        self.feature = feature
+        self.required = required
+        self.target = target
+        super().__init__(
+            f"Dumping QPY version {target}, but version {required} is required for: {feature}."
+        )
+
+
 class QPYLoadingDeprecatedFeatureWarning(QiskitWarning):
     """Visible deprecation warning for QPY loading functions without
     a stable point in the call stack."""
diff --git a/qiskit/qpy/formats.py b/qiskit/qpy/formats.py
index 958bebd8dad9..a48a9ea777fa 100644
--- a/qiskit/qpy/formats.py
+++ b/qiskit/qpy/formats.py
@@ -42,6 +42,24 @@
 FILE_HEADER_PACK = "!6sBBBBQ"
 FILE_HEADER_SIZE = struct.calcsize(FILE_HEADER_PACK)
 
+
+CIRCUIT_HEADER_V12 = namedtuple(
+    "HEADER",
+    [
+        "name_size",
+        "global_phase_type",
+        "global_phase_size",
+        "num_qubits",
+        "num_clbits",
+        "metadata_size",
+        "num_registers",
+        "num_instructions",
+        "num_vars",
+    ],
+)
+CIRCUIT_HEADER_V12_PACK = "!H1cHIIQIQI"
+CIRCUIT_HEADER_V12_SIZE = struct.calcsize(CIRCUIT_HEADER_V12_PACK)
+
 # CIRCUIT_HEADER_V2
 CIRCUIT_HEADER_V2 = namedtuple(
     "HEADER",
@@ -309,6 +327,13 @@
 INITIAL_LAYOUT_BIT_PACK = "!ii"
 INITIAL_LAYOUT_BIT_SIZE = struct.calcsize(INITIAL_LAYOUT_BIT_PACK)
 
+# EXPR_VAR_DECLARATION
+
+EXPR_VAR_DECLARATION = namedtuple("EXPR_VAR_DECLARATION", ["uuid_bytes", "usage", "name_size"])
+EXPR_VAR_DECLARATION_PACK = "!16scH"
+EXPR_VAR_DECLARATION_SIZE = struct.calcsize(EXPR_VAR_DECLARATION_PACK)
+
+
 # EXPRESSION
 
 EXPRESSION_DISCRIMINATOR_SIZE = 1
@@ -351,6 +376,10 @@
 EXPR_VAR_REGISTER_PACK = "!H"
 EXPR_VAR_REGISTER_SIZE = struct.calcsize(EXPR_VAR_REGISTER_PACK)
 
+EXPR_VAR_UUID = namedtuple("EXPR_VAR_UUID", ["var_index"])
+EXPR_VAR_UUID_PACK = "!H"
+EXPR_VAR_UUID_SIZE = struct.calcsize(EXPR_VAR_UUID_PACK)
+
 
 # EXPR_VALUE
 
diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py
index dd0e7fe22693..6ec85115b559 100644
--- a/qiskit/qpy/type_keys.py
+++ b/qiskit/qpy/type_keys.py
@@ -16,6 +16,7 @@
 QPY Type keys for several namespace.
 """
 
+import uuid
 from abc import abstractmethod
 from enum import Enum, IntEnum
 
@@ -471,6 +472,22 @@ def retrieve(cls, type_key):
         raise NotImplementedError
 
 
+class ExprVarDeclaration(TypeKeyBase):
+    """Type keys for the ``EXPR_VAR_DECLARATION`` QPY item."""
+
+    INPUT = b"I"
+    CAPTURE = b"C"
+    LOCAL = b"L"
+
+    @classmethod
+    def assign(cls, obj):
+        raise NotImplementedError
+
+    @classmethod
+    def retrieve(cls, type_key):
+        raise NotImplementedError
+
+
 class ExprType(TypeKeyBase):
     """Type keys for the ``EXPR_TYPE`` QPY item."""
 
@@ -496,9 +513,12 @@ class ExprVar(TypeKeyBase):
 
     CLBIT = b"C"
     REGISTER = b"R"
+    UUID = b"U"
 
     @classmethod
     def assign(cls, obj):
+        if isinstance(obj, uuid.UUID):
+            return cls.UUID
         if isinstance(obj, Clbit):
             return cls.CLBIT
         if isinstance(obj, ClassicalRegister):
diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py
index 766d555bda59..efae9697f187 100644
--- a/test/python/circuit/test_circuit_load_from_qpy.py
+++ b/test/python/circuit/test_circuit_load_from_qpy.py
@@ -22,7 +22,7 @@
 import numpy as np
 
 from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, pulse
-from qiskit.circuit import CASE_DEFAULT
+from qiskit.circuit import CASE_DEFAULT, IfElseOp, WhileLoopOp, SwitchCaseOp
 from qiskit.circuit.classical import expr, types
 from qiskit.circuit.classicalregister import Clbit
 from qiskit.circuit.quantumregister import Qubit
@@ -57,7 +57,7 @@
 from qiskit.circuit.parameter import Parameter
 from qiskit.circuit.parametervector import ParameterVector
 from qiskit.synthesis import LieTrotter, SuzukiTrotter
-from qiskit.qpy import dump, load
+from qiskit.qpy import dump, load, UnsupportedFeatureForVersion, QPY_COMPATIBILITY_VERSION
 from qiskit.quantum_info import Pauli, SparsePauliOp, Clifford
 from qiskit.quantum_info.random import random_unitary
 from qiskit.circuit.controlledgate import ControlledGate
@@ -84,6 +84,26 @@ def assertDeprecatedBitProperties(self, original, roundtripped):
             original_clbits, roundtripped_clbits = zip(*owned_clbits)
             self.assertEqual(original_clbits, roundtripped_clbits)
 
+    def assertMinimalVarEqual(self, left, right):
+        """Replacement for asserting `QuantumCircuit` equality for use in `Var` tests, for use while
+        the `DAGCircuit` does not yet allow full equality checks.  This should be removed and the
+        tests changed to directly call `assertEqual` once possible.
+
+        This filters out instructions that have `QuantumCircuit` parameters in the data comparison
+        (such as control-flow ops), which need to be handled separately."""
+        self.assertEqual(list(left.iter_input_vars()), list(right.iter_input_vars()))
+        self.assertEqual(list(left.iter_declared_vars()), list(right.iter_declared_vars()))
+        self.assertEqual(list(left.iter_captured_vars()), list(right.iter_captured_vars()))
+
+        def filter_ops(data):
+            return [
+                ins
+                for ins in data
+                if not any(isinstance(x, QuantumCircuit) for x in ins.operation.params)
+            ]
+
+        self.assertEqual(filter_ops(left.data), filter_ops(right.data))
+
     def test_qpy_full_path(self):
         """Test full path qpy serialization for basic circuit."""
         qr_a = QuantumRegister(4, "a")
@@ -1760,6 +1780,152 @@ def test_annotated_operations_iterative(self):
             new_circuit = load(fptr)[0]
         self.assertEqual(circuit, new_circuit)
 
+    def test_load_empty_vars(self):
+        """Test loading empty circuits with variables."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        all_vars = {
+            a: expr.lift(False),
+            b: expr.lift(3, type=b.type),
+            expr.Var.new("θψφ", types.Bool()): expr.logic_not(a),
+            expr.Var.new("🐍🐍🐍", types.Uint(8)): expr.bit_and(b, b),
+        }
+
+        inputs = QuantumCircuit(inputs=list(all_vars))
+        with io.BytesIO() as fptr:
+            dump(inputs, fptr)
+            fptr.seek(0)
+            new_inputs = load(fptr)[0]
+        self.assertMinimalVarEqual(inputs, new_inputs)
+        self.assertDeprecatedBitProperties(inputs, new_inputs)
+
+        # Reversed order just to check there's no sorting shenanigans.
+        captures = QuantumCircuit(captures=list(all_vars)[::-1])
+        with io.BytesIO() as fptr:
+            dump(captures, fptr)
+            fptr.seek(0)
+            new_captures = load(fptr)[0]
+        self.assertMinimalVarEqual(captures, new_captures)
+        self.assertDeprecatedBitProperties(captures, new_captures)
+
+        declares = QuantumCircuit(declarations=all_vars)
+        with io.BytesIO() as fptr:
+            dump(declares, fptr)
+            fptr.seek(0)
+            new_declares = load(fptr)[0]
+        self.assertMinimalVarEqual(declares, new_declares)
+        self.assertDeprecatedBitProperties(declares, new_declares)
+
+    def test_load_empty_vars_if(self):
+        """Test loading circuit with vars in if/else closures."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("θψφ", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+        d = expr.Var.new("🐍🐍🐍", types.Uint(8))
+
+        qc = QuantumCircuit(inputs=[a])
+        qc.add_var(b, expr.logic_not(a))
+        qc.add_var(c, expr.lift(0, c.type))
+        with qc.if_test(b) as else_:
+            qc.store(c, expr.lift(3, c.type))
+        with else_:
+            qc.add_var(d, expr.lift(7, d.type))
+
+        with io.BytesIO() as fptr:
+            dump(qc, fptr)
+            fptr.seek(0)
+            new_qc = load(fptr)[0]
+        self.assertMinimalVarEqual(qc, new_qc)
+        self.assertDeprecatedBitProperties(qc, new_qc)
+
+        old_if_else = qc.data[-1].operation
+        new_if_else = new_qc.data[-1].operation
+        # Sanity check for test.
+        self.assertIsInstance(old_if_else, IfElseOp)
+        self.assertIsInstance(new_if_else, IfElseOp)
+        self.assertEqual(len(old_if_else.blocks), len(new_if_else.blocks))
+
+        for old, new in zip(old_if_else.blocks, new_if_else.blocks):
+            self.assertMinimalVarEqual(old, new)
+            self.assertDeprecatedBitProperties(old, new)
+
+    def test_load_empty_vars_while(self):
+        """Test loading circuit with vars in while closures."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("θψφ", types.Bool())
+        c = expr.Var.new("🐍🐍🐍", types.Uint(8))
+
+        qc = QuantumCircuit(inputs=[a])
+        qc.add_var(b, expr.logic_not(a))
+        with qc.while_loop(b):
+            qc.add_var(c, expr.lift(7, c.type))
+
+        with io.BytesIO() as fptr:
+            dump(qc, fptr)
+            fptr.seek(0)
+            new_qc = load(fptr)[0]
+        self.assertMinimalVarEqual(qc, new_qc)
+        self.assertDeprecatedBitProperties(qc, new_qc)
+
+        old_while = qc.data[-1].operation
+        new_while = new_qc.data[-1].operation
+        # Sanity check for test.
+        self.assertIsInstance(old_while, WhileLoopOp)
+        self.assertIsInstance(new_while, WhileLoopOp)
+        self.assertEqual(len(old_while.blocks), len(new_while.blocks))
+
+        for old, new in zip(old_while.blocks, new_while.blocks):
+            self.assertMinimalVarEqual(old, new)
+            self.assertDeprecatedBitProperties(old, new)
+
+    def test_load_empty_vars_switch(self):
+        """Test loading circuit with vars in switch closures."""
+        a = expr.Var.new("🐍🐍🐍", types.Uint(8))
+
+        qc = QuantumCircuit(1, 1, inputs=[a])
+        qc.measure(0, 0)
+        b_outer = qc.add_var("b", False)
+        with qc.switch(a) as case:
+            with case(0):
+                qc.store(b_outer, True)
+            with case(1):
+                qc.store(qc.clbits[0], False)
+            with case(2):
+                # Explicit shadowing.
+                qc.add_var("b", True)
+            with case(3):
+                qc.store(a, expr.lift(1, a.type))
+            with case(case.DEFAULT):
+                pass
+
+        with io.BytesIO() as fptr:
+            dump(qc, fptr)
+            fptr.seek(0)
+            new_qc = load(fptr)[0]
+        self.assertMinimalVarEqual(qc, new_qc)
+        self.assertDeprecatedBitProperties(qc, new_qc)
+
+        old_switch = qc.data[-1].operation
+        new_switch = new_qc.data[-1].operation
+        # Sanity check for test.
+        self.assertIsInstance(old_switch, SwitchCaseOp)
+        self.assertIsInstance(new_switch, SwitchCaseOp)
+        self.assertEqual(len(old_switch.blocks), len(new_switch.blocks))
+
+        for old, new in zip(old_switch.blocks, new_switch.blocks):
+            self.assertMinimalVarEqual(old, new)
+            self.assertDeprecatedBitProperties(old, new)
+
+    @ddt.idata(range(QPY_COMPATIBILITY_VERSION, 12))
+    def test_pre_v12_rejects_standalone_var(self, version):
+        """Test that dumping to older QPY versions rejects standalone vars."""
+        a = expr.Var.new("a", types.Bool())
+        qc = QuantumCircuit(inputs=[a])
+        with io.BytesIO() as fptr, self.assertRaisesRegex(
+            UnsupportedFeatureForVersion, "version 12 is required.*realtime variables"
+        ):
+            dump(qc, fptr, version=version)
+
 
 class TestSymengineLoadFromQPY(QiskitTestCase):
     """Test use of symengine in qpy set of methods."""
diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py
index 345d9dc0a447..58ee1abc2a2f 100755
--- a/test/qpy_compat/test_qpy.py
+++ b/test/qpy_compat/test_qpy.py
@@ -754,6 +754,54 @@ def generate_control_flow_expr():
     return [qc1, qc2, qc3, qc4]
 
 
+def generate_standalone_var():
+    """Circuits that use standalone variables."""
+    import uuid
+    from qiskit.circuit.classical import expr, types
+
+    # This is the low-level, non-preferred way to construct variables, but we need the UUIDs to be
+    # deterministic between separate invocations of the script.
+    uuids = [
+        uuid.UUID(bytes=b"hello, qpy world", version=4),
+        uuid.UUID(bytes=b"not a good uuid4", version=4),
+        uuid.UUID(bytes=b"but it's ok here", version=4),
+        uuid.UUID(bytes=b"any old 16 bytes", version=4),
+        uuid.UUID(bytes=b"and another load", version=4),
+    ]
+    a = expr.Var(uuids[0], types.Bool(), name="a")
+    b = expr.Var(uuids[1], types.Bool(), name="θψφ")
+    b_other = expr.Var(uuids[2], types.Bool(), name=b.name)
+    c = expr.Var(uuids[3], types.Uint(8), name="🐍🐍🐍")
+    d = expr.Var(uuids[4], types.Uint(8), name="d")
+
+    qc = QuantumCircuit(1, 1, inputs=[a], name="standalone_var")
+    qc.add_var(b, expr.logic_not(a))
+
+    qc.add_var(c, expr.lift(0, c.type))
+    with qc.if_test(b) as else_:
+        qc.store(c, expr.lift(3, c.type))
+        with qc.while_loop(b):
+            qc.add_var(c, expr.lift(7, c.type))
+    with else_:
+        qc.add_var(d, expr.lift(7, d.type))
+
+    qc.measure(0, 0)
+    with qc.switch(c) as case:
+        with case(0):
+            qc.store(b, True)
+        with case(1):
+            qc.store(qc.clbits[0], False)
+        with case(2):
+            # Explicit shadowing.
+            qc.add_var(b_other, True)
+        with case(3):
+            qc.store(a, False)
+        with case(case.DEFAULT):
+            pass
+
+    return [qc]
+
+
 def generate_circuits(version_parts):
     """Generate reference circuits."""
     output_circuits = {
@@ -802,6 +850,8 @@ def generate_circuits(version_parts):
         output_circuits["clifford.qpy"] = generate_clifford_circuits()
     if version_parts >= (1, 0, 0):
         output_circuits["annotated.qpy"] = generate_annotated_circuits()
+    if version_parts >= (1, 1, 0):
+        output_circuits["standalone_vars.qpy"] = generate_standalone_var()
     return output_circuits
 
 

From 958cc9b565f52e7326165d3b631d455e3c601972 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Tue, 30 Apr 2024 10:42:24 -0400
Subject: [PATCH 039/179] Avoid intermediate DAGCircuit construction in 2q
 synthesis (#12179)

This commit builds on #12109 which added a dag output to the two qubit
decomposers that are then used by unitary synthesis to add a mode of
operation in unitary synthesis that avoids intermediate dag creation.
To do this efficiently this requires changing the UnitarySynthesis pass
to rebuild the DAG instead of doing a node substitution.
---
 .../passes/synthesis/unitary_synthesis.py     | 131 +++++++++++++-----
 1 file changed, 98 insertions(+), 33 deletions(-)

diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
index 5d919661a838..a30411d16a93 100644
--- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
@@ -39,6 +39,7 @@
 from qiskit.synthesis.two_qubit.two_qubit_decompose import (
     TwoQubitBasisDecomposer,
     TwoQubitWeylDecomposition,
+    GATE_NAME_MAP,
 )
 from qiskit.quantum_info import Operator
 from qiskit.circuit import ControlFlowOp, Gate, Parameter
@@ -293,7 +294,7 @@ def __init__(
         natural_direction: bool | None = None,
         synth_gates: list[str] | None = None,
         method: str = "default",
-        min_qubits: int = None,
+        min_qubits: int = 0,
         plugin_config: dict = None,
         target: Target = None,
     ):
@@ -499,27 +500,55 @@ def _run_main_loop(
                 ]
             )
 
-        for node in dag.named_nodes(*self._synth_gates):
-            if self._min_qubits is not None and len(node.qargs) < self._min_qubits:
-                continue
-            synth_dag = None
-            unitary = node.op.to_matrix()
-            n_qubits = len(node.qargs)
-            if (plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits) or (
-                plugin_method.min_qubits is not None and n_qubits < plugin_method.min_qubits
-            ):
-                method, kwargs = default_method, default_kwargs
+        out_dag = dag.copy_empty_like()
+        for node in dag.topological_op_nodes():
+            if node.op.name == "unitary" and len(node.qargs) >= self._min_qubits:
+                synth_dag = None
+                unitary = node.op.to_matrix()
+                n_qubits = len(node.qargs)
+                if (
+                    plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits
+                ) or (plugin_method.min_qubits is not None and n_qubits < plugin_method.min_qubits):
+                    method, kwargs = default_method, default_kwargs
+                else:
+                    method, kwargs = plugin_method, plugin_kwargs
+                if method.supports_coupling_map:
+                    kwargs["coupling_map"] = (
+                        self._coupling_map,
+                        [qubit_indices[x] for x in node.qargs],
+                    )
+                synth_dag = method.run(unitary, **kwargs)
+                if synth_dag is None:
+                    out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False)
+                    continue
+                if isinstance(synth_dag, DAGCircuit):
+                    qubit_map = dict(zip(synth_dag.qubits, node.qargs))
+                    for node in synth_dag.topological_op_nodes():
+                        out_dag.apply_operation_back(
+                            node.op, (qubit_map[x] for x in node.qargs), check=False
+                        )
+                    out_dag.global_phase += synth_dag.global_phase
+                else:
+                    node_list, global_phase, gate = synth_dag
+                    qubits = node.qargs
+                    for (
+                        op_name,
+                        params,
+                        qargs,
+                    ) in node_list:
+                        if op_name == "USER_GATE":
+                            op = gate
+                        else:
+                            op = GATE_NAME_MAP[op_name](*params)
+                        out_dag.apply_operation_back(
+                            op,
+                            (qubits[x] for x in qargs),
+                            check=False,
+                        )
+                    out_dag.global_phase += global_phase
             else:
-                method, kwargs = plugin_method, plugin_kwargs
-            if method.supports_coupling_map:
-                kwargs["coupling_map"] = (
-                    self._coupling_map,
-                    [qubit_indices[x] for x in node.qargs],
-                )
-            synth_dag = method.run(unitary, **kwargs)
-            if synth_dag is not None:
-                dag.substitute_node_with_dag(node, synth_dag)
-        return dag
+                out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False)
+        return out_dag
 
 
 def _build_gate_lengths(props=None, target=None):
@@ -893,6 +922,20 @@ def run(self, unitary, **options):
                 decomposers2q = [decomposer2q] if decomposer2q is not None else []
             # choose the cheapest output among synthesized circuits
             synth_circuits = []
+            # If we have a single TwoQubitBasisDecomposer skip dag creation as we don't need to
+            # store and can instead manually create the synthesized gates directly in the output dag
+            if len(decomposers2q) == 1 and isinstance(decomposers2q[0], TwoQubitBasisDecomposer):
+                preferred_direction = _preferred_direction(
+                    decomposers2q[0],
+                    qubits,
+                    natural_direction,
+                    coupling_map,
+                    gate_lengths,
+                    gate_errors,
+                )
+                return self._synth_su4_no_dag(
+                    unitary, decomposers2q[0], preferred_direction, approximation_degree
+                )
             for decomposer2q in decomposers2q:
                 preferred_direction = _preferred_direction(
                     decomposer2q, qubits, natural_direction, coupling_map, gate_lengths, gate_errors
@@ -919,6 +962,24 @@ def run(self, unitary, **options):
             return synth_circuit
         return circuit_to_dag(synth_circuit)
 
+    def _synth_su4_no_dag(self, unitary, decomposer2q, preferred_direction, approximation_degree):
+        approximate = not approximation_degree == 1.0
+        synth_circ = decomposer2q._inner_decomposer(unitary, approximate=approximate)
+        if not preferred_direction:
+            return (synth_circ, synth_circ.global_phase, decomposer2q.gate)
+
+        synth_direction = None
+        # if the gates in synthesis are in the opposite direction of the preferred direction
+        # resynthesize a new operator which is the original conjugated by swaps.
+        # this new operator is doubly mirrored from the original and is locally equivalent.
+        for op_name, _params, qubits in synth_circ:
+            if op_name in {"USER_GATE", "cx"}:
+                synth_direction = qubits
+        if synth_direction is not None and synth_direction != preferred_direction:
+            # TODO: Avoid using a dag to correct the synthesis direction
+            return self._reversed_synth_su4(unitary, decomposer2q, approximation_degree)
+        return (synth_circ, synth_circ.global_phase, decomposer2q.gate)
+
     def _synth_su4(self, su4_mat, decomposer2q, preferred_direction, approximation_degree):
         approximate = not approximation_degree == 1.0
         synth_circ = decomposer2q(su4_mat, approximate=approximate, use_dag=True)
@@ -932,16 +993,20 @@ def _synth_su4(self, su4_mat, decomposer2q, preferred_direction, approximation_d
             if inst.op.num_qubits == 2:
                 synth_direction = [synth_circ.find_bit(q).index for q in inst.qargs]
         if synth_direction is not None and synth_direction != preferred_direction:
-            su4_mat_mm = su4_mat.copy()
-            su4_mat_mm[[1, 2]] = su4_mat_mm[[2, 1]]
-            su4_mat_mm[:, [1, 2]] = su4_mat_mm[:, [2, 1]]
-            synth_circ = decomposer2q(su4_mat_mm, approximate=approximate, use_dag=True)
-            out_dag = DAGCircuit()
-            out_dag.global_phase = synth_circ.global_phase
-            out_dag.add_qubits(list(reversed(synth_circ.qubits)))
-            flip_bits = out_dag.qubits[::-1]
-            for node in synth_circ.topological_op_nodes():
-                qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs)
-                out_dag.apply_operation_back(node.op, qubits, check=False)
-            return out_dag
+            return self._reversed_synth_su4(su4_mat, decomposer2q, approximation_degree)
         return synth_circ
+
+    def _reversed_synth_su4(self, su4_mat, decomposer2q, approximation_degree):
+        approximate = not approximation_degree == 1.0
+        su4_mat_mm = su4_mat.copy()
+        su4_mat_mm[[1, 2]] = su4_mat_mm[[2, 1]]
+        su4_mat_mm[:, [1, 2]] = su4_mat_mm[:, [2, 1]]
+        synth_circ = decomposer2q(su4_mat_mm, approximate=approximate, use_dag=True)
+        out_dag = DAGCircuit()
+        out_dag.global_phase = synth_circ.global_phase
+        out_dag.add_qubits(list(reversed(synth_circ.qubits)))
+        flip_bits = out_dag.qubits[::-1]
+        for node in synth_circ.topological_op_nodes():
+            qubits = tuple(flip_bits[synth_circ.find_bit(x).index] for x in node.qargs)
+            out_dag.apply_operation_back(node.op, qubits, check=False)
+        return out_dag

From 95476b747163479fca5ad2ec81b4b3e9fd7fd657 Mon Sep 17 00:00:00 2001
From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Date: Tue, 30 Apr 2024 22:11:04 +0400
Subject: [PATCH 040/179] fix a type hint and add tests for iterable (#12309)

---
 qiskit/primitives/backend_sampler_v2.py         |  2 +-
 .../primitives/test_backend_estimator_v2.py     | 12 ++++++++++++
 .../primitives/test_backend_sampler_v2.py       | 17 +++++++++++++++++
 .../primitives/test_statevector_estimator.py    |  9 +++++++++
 .../primitives/test_statevector_sampler.py      | 16 ++++++++++++++++
 5 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py
index 51d1ded1500c..87507e1d54d0 100644
--- a/qiskit/primitives/backend_sampler_v2.py
+++ b/qiskit/primitives/backend_sampler_v2.py
@@ -142,7 +142,7 @@ def _validate_pubs(self, pubs: list[SamplerPub]):
                     UserWarning,
                 )
 
-    def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[PubResult]:
+    def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[PubResult]:
         pub_dict = defaultdict(list)
         # consolidate pubs with the same number of shots
         for i, pub in enumerate(pubs):
diff --git a/test/python/primitives/test_backend_estimator_v2.py b/test/python/primitives/test_backend_estimator_v2.py
index 2af6b15b8777..6728d57e3fdd 100644
--- a/test/python/primitives/test_backend_estimator_v2.py
+++ b/test/python/primitives/test_backend_estimator_v2.py
@@ -461,6 +461,18 @@ def test_job_size_limit_backend_v1(self):
             estimator.run([(qc, op, param_list)] * k).result()
         self.assertEqual(run_mock.call_count, 10)
 
+    def test_iter_pub(self):
+        """test for an iterable of pubs"""
+        backend = BasicSimulator()
+        circuit = self.ansatz.assign_parameters([0, 1, 1, 2, 3, 5])
+        pm = generate_preset_pass_manager(optimization_level=0, backend=backend)
+        circuit = pm.run(circuit)
+        estimator = BackendEstimatorV2(backend=backend, options=self._options)
+        observable = self.observable.apply_layout(circuit.layout)
+        result = estimator.run(iter([(circuit, observable), (circuit, observable)])).result()
+        np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733], rtol=self._rtol)
+        np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733], rtol=self._rtol)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py
index 64a26471a33e..dd58920689a3 100644
--- a/test/python/primitives/test_backend_sampler_v2.py
+++ b/test/python/primitives/test_backend_sampler_v2.py
@@ -733,6 +733,23 @@ def test_job_size_limit_backend_v1(self):
         self._assert_allclose(result[0].data.meas, np.array({0: self._shots}))
         self._assert_allclose(result[1].data.meas, np.array({1: self._shots}))
 
+    def test_iter_pub(self):
+        """Test of an iterable of pubs"""
+        backend = BasicSimulator()
+        qc = QuantumCircuit(1)
+        qc.measure_all()
+        qc2 = QuantumCircuit(1)
+        qc2.x(0)
+        qc2.measure_all()
+        sampler = BackendSamplerV2(backend=backend)
+        result = sampler.run(iter([qc, qc2]), shots=self._shots).result()
+        self.assertIsInstance(result, PrimitiveResult)
+        self.assertEqual(len(result), 2)
+        self.assertIsInstance(result[0], PubResult)
+        self.assertIsInstance(result[1], PubResult)
+        self._assert_allclose(result[0].data.meas, np.array({0: self._shots}))
+        self._assert_allclose(result[1].data.meas, np.array({1: self._shots}))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/primitives/test_statevector_estimator.py b/test/python/primitives/test_statevector_estimator.py
index 15c022f770cd..117ead6717ad 100644
--- a/test/python/primitives/test_statevector_estimator.py
+++ b/test/python/primitives/test_statevector_estimator.py
@@ -281,6 +281,15 @@ def test_precision_seed(self):
         result = job.result()
         np.testing.assert_allclose(result[0].data.evs, [1.5555572817900956])
 
+    def test_iter_pub(self):
+        """test for an iterable of pubs"""
+        estimator = StatevectorEstimator()
+        circuit = self.ansatz.assign_parameters([0, 1, 1, 2, 3, 5])
+        observable = self.observable.apply_layout(circuit.layout)
+        result = estimator.run(iter([(circuit, observable), (circuit, observable)])).result()
+        np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733])
+        np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/primitives/test_statevector_sampler.py b/test/python/primitives/test_statevector_sampler.py
index cd0622b18de7..1a8ed0402e58 100644
--- a/test/python/primitives/test_statevector_sampler.py
+++ b/test/python/primitives/test_statevector_sampler.py
@@ -621,6 +621,22 @@ def test_no_cregs(self):
         self.assertEqual(len(result), 1)
         self.assertEqual(len(result[0].data), 0)
 
+    def test_iter_pub(self):
+        """Test of an iterable of pubs"""
+        qc = QuantumCircuit(1)
+        qc.measure_all()
+        qc2 = QuantumCircuit(1)
+        qc2.x(0)
+        qc2.measure_all()
+        sampler = StatevectorSampler()
+        result = sampler.run(iter([qc, qc2]), shots=self._shots).result()
+        self.assertIsInstance(result, PrimitiveResult)
+        self.assertEqual(len(result), 2)
+        self.assertIsInstance(result[0], PubResult)
+        self.assertIsInstance(result[1], PubResult)
+        self._assert_allclose(result[0].data.meas, np.array({0: self._shots}))
+        self._assert_allclose(result[1].data.meas, np.array({1: self._shots}))
+
 
 if __name__ == "__main__":
     unittest.main()

From febc16cb43ef7d9663c6363e9b92d3cca9036115 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Tue, 30 Apr 2024 17:28:46 -0400
Subject: [PATCH 041/179] Oxidize the numeric code in the Isometry gate class
 (#12197)

* Oxidize the numeric code in the Isometry gate class

This commit ports the numeric portion of the Isometry gate class to
rust. While this will likely improve the performance slightly this move
is more to make isolate this code from blas/lapack in numpy. We're
hitting some stability issues on arm64 mac in CI and moving this code to
rust should hopefully fix this issue. As this is more for functional
reasons no real performance tuning was done on this port, there are
likely several opportunities to improve the runtime performance of the
code.

* Remove unused import

* Use rust impl for small utility functions too

* Oxidize the linalg in UCGate too

The UCGate class is used almost exclusively by the Isometry class to
build up the definition of the isometry circuit. There were also some
linear algebra inside the function which could also be the source of the
stability issues we were seeing on arm64. This commit ports this
function as part of the larger isometry migration.

* Migrate another numeric helper method of UCGate

* Remove now unused code paths

* Remove bitstring usage with bitwise ops

This commit removes the use of bit string manipulations that were
faithfully ported from the original python logic (but left a bad taste
in my mouth) into more efficient bitwise operations (which were
possible in the original python too).

* Mostly replace Vec usage with bitwise operations

The use of intermediate Vec as proxy bitstrings was originally
ported nearly exactly from the python implementation. But since
everything is working now this commit switches to use bitwise operations
where it makes sense as this will be more efficient.

* Apply suggestions from code review

Co-authored-by: Jake Lishman 

* Remove python side call sites

* Fix integer typing in uc_gate.rs

* Simplify basis state bitshift loop logic

* Build set of control labels outside construct_basis_states

* Use 2 element array for reverse_qubit_state

* Micro optimize reverse_qubit_state

* Use 1d numpy arrays for diagonal inputs

* Fix lint

* Update crates/accelerate/src/isometry.rs

Co-authored-by: John Lapeyre 

* Add back sqrt() accidentally removed by inline suggestion

* Use a constant for rz pi/2 elements

---------

Co-authored-by: Jake Lishman 
Co-authored-by: John Lapeyre 
---
 Cargo.lock                                    |  10 +
 crates/accelerate/Cargo.toml                  |   1 +
 crates/accelerate/src/isometry.rs             | 367 ++++++++++++++++++
 crates/accelerate/src/lib.rs                  |   2 +
 crates/accelerate/src/two_qubit_decompose.rs  |   2 +-
 crates/accelerate/src/uc_gate.rs              | 163 ++++++++
 crates/pyext/src/lib.rs                       |  11 +-
 qiskit/__init__.py                            |   2 +
 .../library/generalized_gates/isometry.py     | 286 ++------------
 .../circuit/library/generalized_gates/uc.py   | 100 +----
 10 files changed, 596 insertions(+), 348 deletions(-)
 create mode 100644 crates/accelerate/src/isometry.rs
 create mode 100644 crates/accelerate/src/uc_gate.rs

diff --git a/Cargo.lock b/Cargo.lock
index 6859c622967d..dee434e870a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -589,6 +589,15 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "jod-thread"
 version = "0.1.2"
@@ -1079,6 +1088,7 @@ dependencies = [
  "faer-ext",
  "hashbrown 0.14.3",
  "indexmap 2.2.6",
+ "itertools 0.12.1",
  "ndarray",
  "num-bigint",
  "num-complex",
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index 05ca3f5b6395..a43fdc6ff506 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -21,6 +21,7 @@ num-complex = "0.4"
 num-bigint = "0.4"
 rustworkx-core = "0.14"
 faer = "0.18.2"
+itertools = "0.12.1"
 qiskit-circuit.workspace = true
 
 [dependencies.smallvec]
diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs
new file mode 100644
index 000000000000..8d0761666bb6
--- /dev/null
+++ b/crates/accelerate/src/isometry.rs
@@ -0,0 +1,367 @@
+// 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 std::ops::BitAnd;
+
+use approx::abs_diff_eq;
+use num_complex::{Complex64, ComplexFloat};
+use pyo3::prelude::*;
+use pyo3::wrap_pyfunction;
+use pyo3::Python;
+
+use hashbrown::HashSet;
+use itertools::Itertools;
+use ndarray::prelude::*;
+use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2};
+
+use crate::two_qubit_decompose::ONE_QUBIT_IDENTITY;
+
+/// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or
+/// basis_state=1 respectively
+#[pyfunction]
+pub fn reverse_qubit_state(
+    py: Python,
+    state: [Complex64; 2],
+    basis_state: usize,
+    epsilon: f64,
+) -> PyObject {
+    reverse_qubit_state_inner(&state, basis_state, epsilon)
+        .into_pyarray_bound(py)
+        .into()
+}
+
+#[inline(always)]
+fn l2_norm(vec: &[Complex64]) -> f64 {
+    vec.iter()
+        .fold(0., |acc, elem| acc + elem.norm_sqr())
+        .sqrt()
+}
+
+fn reverse_qubit_state_inner(
+    state: &[Complex64; 2],
+    basis_state: usize,
+    epsilon: f64,
+) -> Array2 {
+    let r = l2_norm(state);
+    let r_inv = 1. / r;
+    if r < epsilon {
+        Array2::eye(2)
+    } else if basis_state == 0 {
+        array![
+            [state[0].conj() * r_inv, state[1].conj() * r_inv],
+            [-state[1] * r_inv, state[0] * r_inv],
+        ]
+    } else {
+        array![
+            [-state[1] * r_inv, state[0] * r_inv],
+            [state[0].conj() * r_inv, state[1].conj() * r_inv],
+        ]
+    }
+}
+
+/// This method finds the single-qubit gates for a UCGate to disentangle a qubit:
+/// we consider the n-qubit state v[:,0] starting with k zeros (in the computational basis).
+/// The qubit with label n-s-1 is disentangled into the basis state k_s(k,s).
+
+#[pyfunction]
+pub fn find_squs_for_disentangling(
+    py: Python,
+    v: PyReadonlyArray2,
+    k: usize,
+    s: usize,
+    epsilon: f64,
+    n: usize,
+) -> Vec {
+    let v = v.as_array();
+    let k_prime = 0;
+    let i_start = if b(k, s + 1) == 0 {
+        a(k, s + 1)
+    } else {
+        a(k, s + 1) + 1
+    };
+    let mut output: Vec> = (0..i_start).map(|_| Array2::eye(2)).collect();
+    let mut squs: Vec> = (i_start..2_usize.pow((n - s - 1) as u32))
+        .map(|i| {
+            reverse_qubit_state_inner(
+                &[
+                    v[[2 * i * 2_usize.pow(s as u32) + b(k, s), k_prime]],
+                    v[[(2 * i + 1) * 2_usize.pow(s as u32) + b(k, s), k_prime]],
+                ],
+                k_s(k, s),
+                epsilon,
+            )
+        })
+        .collect();
+    output.append(&mut squs);
+    output
+        .into_iter()
+        .map(|x| x.into_pyarray_bound(py).into())
+        .collect()
+}
+
+#[pyfunction]
+pub fn apply_ucg(
+    py: Python,
+    m: PyReadonlyArray2,
+    k: usize,
+    single_qubit_gates: Vec>,
+) -> PyObject {
+    let mut m = m.as_array().to_owned();
+    let shape = m.shape();
+    let num_qubits = shape[0].ilog2();
+    let num_col = shape[1];
+    let spacing: usize = 2_usize.pow(num_qubits - k as u32 - 1);
+    for j in 0..2_usize.pow(num_qubits - 1) {
+        let i = (j / spacing) * spacing + j;
+        let gate_index = i / (2_usize.pow(num_qubits - k as u32));
+        for col in 0..num_col {
+            let gate = single_qubit_gates[gate_index].as_array();
+            let a = m[[i, col]];
+            let b = m[[i + spacing, col]];
+            m[[i, col]] = gate[[0, 0]] * a + gate[[0, 1]] * b;
+            m[[i + spacing, col]] = gate[[1, 0]] * a + gate[[1, 1]] * b;
+        }
+    }
+    m.into_pyarray_bound(py).into()
+}
+
+#[inline(always)]
+fn bin_to_int(bin: &[u8]) -> usize {
+    bin.iter()
+        .fold(0_usize, |acc, digit| (acc << 1) + *digit as usize)
+}
+
+#[pyfunction]
+pub fn apply_diagonal_gate(
+    py: Python,
+    m: PyReadonlyArray2,
+    action_qubit_labels: Vec,
+    diag: PyReadonlyArray1,
+) -> PyResult {
+    let diag = diag.as_slice()?;
+    let mut m = m.as_array().to_owned();
+    let shape = m.shape();
+    let num_qubits = shape[0].ilog2();
+    let num_col = shape[1];
+    for state in std::iter::repeat([0_u8, 1_u8])
+        .take(num_qubits as usize)
+        .multi_cartesian_product()
+    {
+        let diag_index = action_qubit_labels
+            .iter()
+            .fold(0_usize, |acc, i| (acc << 1) + state[*i] as usize);
+        let i = bin_to_int(&state);
+        for j in 0..num_col {
+            m[[i, j]] = diag[diag_index] * m[[i, j]]
+        }
+    }
+    Ok(m.into_pyarray_bound(py).into())
+}
+
+#[pyfunction]
+pub fn apply_diagonal_gate_to_diag(
+    mut m_diagonal: Vec,
+    action_qubit_labels: Vec,
+    diag: PyReadonlyArray1,
+    num_qubits: usize,
+) -> PyResult> {
+    let diag = diag.as_slice()?;
+    if m_diagonal.is_empty() {
+        return Ok(m_diagonal);
+    }
+    for state in std::iter::repeat([0_u8, 1_u8])
+        .take(num_qubits)
+        .multi_cartesian_product()
+        .take(m_diagonal.len())
+    {
+        let diag_index = action_qubit_labels
+            .iter()
+            .fold(0_usize, |acc, i| (acc << 1) + state[*i] as usize);
+        let i = bin_to_int(&state);
+        m_diagonal[i] *= diag[diag_index]
+    }
+    Ok(m_diagonal)
+}
+
+/// Helper method for _apply_multi_controlled_gate. This constructs the basis states the MG gate
+/// is acting on for a specific state state_free of the qubits we neither control nor act on
+fn construct_basis_states(
+    state_free: &[u8],
+    control_set: &HashSet,
+    target_label: usize,
+) -> [usize; 2] {
+    let size = state_free.len() + control_set.len() + 1;
+    let mut e1: usize = 0;
+    let mut e2: usize = 0;
+    let mut j = 0;
+    for i in 0..size {
+        e1 <<= 1;
+        e2 <<= 1;
+        if control_set.contains(&i) {
+            e1 += 1;
+            e2 += 1;
+        } 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
+        }
+    }
+    [e1, e2]
+}
+
+#[pyfunction]
+pub fn apply_multi_controlled_gate(
+    py: Python,
+    m: PyReadonlyArray2,
+    control_labels: Vec,
+    target_label: usize,
+    gate: PyReadonlyArray2,
+) -> PyObject {
+    let mut m = m.as_array().to_owned();
+    let gate = gate.as_array();
+    let shape = m.shape();
+    let num_qubits = shape[0].ilog2();
+    let num_col = shape[1];
+    let free_qubits = num_qubits as usize - control_labels.len() - 1;
+    let control_set: HashSet = control_labels.into_iter().collect();
+    if free_qubits == 0 {
+        let [e1, e2] = construct_basis_states(&[], &control_set, target_label);
+        for i in 0..num_col {
+            let temp: Vec<_> = gate
+                .dot(&aview2(&[[m[[e1, i]]], [m[[e2, i]]]]))
+                .into_iter()
+                .take(2)
+                .collect();
+            m[[e1, i]] = temp[0];
+            m[[e2, i]] = temp[1];
+        }
+        return m.into_pyarray_bound(py).into();
+    }
+    for state_free in std::iter::repeat([0_u8, 1_u8])
+        .take(free_qubits)
+        .multi_cartesian_product()
+    {
+        let [e1, e2] = construct_basis_states(&state_free, &control_set, target_label);
+        for i in 0..num_col {
+            let temp: Vec<_> = gate
+                .dot(&aview2(&[[m[[e1, i]]], [m[[e2, i]]]]))
+                .into_iter()
+                .take(2)
+                .collect();
+            m[[e1, i]] = temp[0];
+            m[[e2, i]] = temp[1];
+        }
+    }
+    m.into_pyarray_bound(py).into()
+}
+
+#[pyfunction]
+pub fn ucg_is_identity_up_to_global_phase(
+    single_qubit_gates: Vec>,
+    epsilon: f64,
+) -> bool {
+    let global_phase: Complex64 = if single_qubit_gates[0].as_array()[[0, 0]].abs() >= epsilon {
+        single_qubit_gates[0].as_array()[[0, 0]].finv()
+    } else {
+        return false;
+    };
+    for raw_gate in single_qubit_gates {
+        let gate = raw_gate.as_array();
+        if !abs_diff_eq!(
+            gate.mapv(|x| x * global_phase),
+            aview2(&ONE_QUBIT_IDENTITY),
+            epsilon = 1e-8 // Default tolerance from numpy for allclose()
+        ) {
+            return false;
+        }
+    }
+    true
+}
+
+#[pyfunction]
+fn diag_is_identity_up_to_global_phase(diag: Vec, epsilon: f64) -> bool {
+    let global_phase: Complex64 = if diag[0].abs() >= epsilon {
+        diag[0].finv()
+    } else {
+        return false;
+    };
+    for d in diag {
+        if (global_phase * d - 1.0).abs() >= epsilon {
+            return false;
+        }
+    }
+    true
+}
+
+#[pyfunction]
+pub fn merge_ucgate_and_diag(
+    py: Python,
+    single_qubit_gates: Vec>,
+    diag: Vec,
+) -> Vec {
+    single_qubit_gates
+        .iter()
+        .enumerate()
+        .map(|(i, raw_gate)| {
+            let gate = raw_gate.as_array();
+            let res = aview2(&[
+                [diag[2 * i], Complex64::new(0., 0.)],
+                [Complex64::new(0., 0.), diag[2 * i + 1]],
+            ])
+            .dot(&gate);
+            res.into_pyarray_bound(py).into()
+        })
+        .collect()
+}
+
+#[inline(always)]
+#[pyfunction]
+fn k_s(k: usize, s: usize) -> usize {
+    if k == 0 {
+        0
+    } else {
+        let filter = 1 << s;
+        k.bitand(filter) >> s
+    }
+}
+
+#[inline(always)]
+#[pyfunction]
+fn a(k: usize, s: usize) -> usize {
+    k / 2_usize.pow(s as u32)
+}
+
+#[inline(always)]
+#[pyfunction]
+fn b(k: usize, s: usize) -> usize {
+    k - (a(k, s) * 2_usize.pow(s as u32))
+}
+
+#[pymodule]
+pub fn isometry(m: &Bound) -> PyResult<()> {
+    m.add_wrapped(wrap_pyfunction!(diag_is_identity_up_to_global_phase))?;
+    m.add_wrapped(wrap_pyfunction!(find_squs_for_disentangling))?;
+    m.add_wrapped(wrap_pyfunction!(reverse_qubit_state))?;
+    m.add_wrapped(wrap_pyfunction!(apply_ucg))?;
+    m.add_wrapped(wrap_pyfunction!(apply_diagonal_gate))?;
+    m.add_wrapped(wrap_pyfunction!(apply_diagonal_gate_to_diag))?;
+    m.add_wrapped(wrap_pyfunction!(apply_multi_controlled_gate))?;
+    m.add_wrapped(wrap_pyfunction!(ucg_is_identity_up_to_global_phase))?;
+    m.add_wrapped(wrap_pyfunction!(merge_ucgate_and_diag))?;
+    m.add_wrapped(wrap_pyfunction!(a))?;
+    m.add_wrapped(wrap_pyfunction!(b))?;
+    m.add_wrapped(wrap_pyfunction!(k_s))?;
+    Ok(())
+}
diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs
index bb7621dce346..0af8ea6a0fce 100644
--- a/crates/accelerate/src/lib.rs
+++ b/crates/accelerate/src/lib.rs
@@ -19,6 +19,7 @@ pub mod dense_layout;
 pub mod edge_collections;
 pub mod error_map;
 pub mod euler_one_qubit_decomposer;
+pub mod isometry;
 pub mod nlayout;
 pub mod optimize_1q_gates;
 pub mod pauli_exp_val;
@@ -28,6 +29,7 @@ pub mod sampled_exp_val;
 pub mod sparse_pauli_op;
 pub mod stochastic_swap;
 pub mod two_qubit_decompose;
+pub mod uc_gate;
 pub mod utils;
 pub mod vf2_layout;
 
diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs
index 7dcb273ac163..5e833bd86fda 100644
--- a/crates/accelerate/src/two_qubit_decompose.rs
+++ b/crates/accelerate/src/two_qubit_decompose.rs
@@ -60,7 +60,7 @@ const TWO_PI: f64 = 2.0 * PI;
 
 const C1: c64 = c64 { re: 1.0, im: 0.0 };
 
-static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [
+pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [
     [Complex64::new(1., 0.), Complex64::new(0., 0.)],
     [Complex64::new(0., 0.), Complex64::new(1., 0.)],
 ];
diff --git a/crates/accelerate/src/uc_gate.rs b/crates/accelerate/src/uc_gate.rs
new file mode 100644
index 000000000000..3a5f74a6f0b1
--- /dev/null
+++ b/crates/accelerate/src/uc_gate.rs
@@ -0,0 +1,163 @@
+// 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 num_complex::{Complex64, ComplexFloat};
+use pyo3::prelude::*;
+use pyo3::wrap_pyfunction;
+use pyo3::Python;
+use std::f64::consts::{FRAC_1_SQRT_2, PI};
+
+use faer_ext::{IntoFaerComplex, IntoNdarrayComplex};
+use ndarray::prelude::*;
+use numpy::{IntoPyArray, PyReadonlyArray2};
+
+use crate::euler_one_qubit_decomposer::det_one_qubit;
+
+const PI2: f64 = PI / 2.;
+const EPS: f64 = 1e-10;
+
+// These constants are the non-zero elements of an RZ gate's unitary with an
+// angle of pi / 2
+const RZ_PI2_11: Complex64 = Complex64::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2);
+const RZ_PI2_00: Complex64 = Complex64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2);
+
+/// This method implements the decomposition given in equation (3) in
+/// https://arxiv.org/pdf/quant-ph/0410066.pdf.
+///
+/// The decomposition is used recursively to decompose uniformly controlled gates.
+///
+/// a,b = single qubit unitaries
+/// v,u,r = outcome of the decomposition given in the reference mentioned above
+///
+/// (see there for the details).
+fn demultiplex_single_uc(
+    a: ArrayView2,
+    b: ArrayView2,
+) -> [Array2; 3] {
+    let x = a.dot(&b.mapv(|x| x.conj()).t());
+    let det_x = det_one_qubit(x.view());
+    let x11 = x[[0, 0]] / det_x.sqrt();
+    let phi = det_x.arg();
+
+    let r1 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. - x11.arg())).exp();
+    let r2 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. + x11.arg() + PI)).exp();
+
+    let r = array![[r1, Complex64::new(0., 0.)], [Complex64::new(0., 0.), r2],];
+
+    let decomp = r
+        .dot(&x)
+        .dot(&r)
+        .view()
+        .into_faer_complex()
+        .complex_eigendecomposition();
+    let mut u: Array2 = decomp.u().into_ndarray_complex().to_owned();
+    let s = decomp.s().column_vector();
+    let mut diag: Array1 =
+        Array1::from_shape_fn(u.shape()[0], |i| s[i].to_num_complex());
+
+    // If d is not equal to diag(i,-i), then we put it into this "standard" form
+    // (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging
+    // the eigenvalues and eigenvectors
+    if (diag[0] + Complex64::new(0., 1.)).abs() < EPS {
+        diag = diag.slice(s![..;-1]).to_owned();
+        u = u.slice(s![.., ..;-1]).to_owned();
+    }
+    diag.mapv_inplace(|x| x.sqrt());
+    let d = Array2::from_diag(&diag);
+    let v = d
+        .dot(&u.mapv(|x| x.conj()).t())
+        .dot(&r.mapv(|x| x.conj()).t())
+        .dot(&b);
+    [v, u, r]
+}
+
+#[pyfunction]
+pub fn dec_ucg_help(
+    py: Python,
+    sq_gates: Vec>,
+    num_qubits: u32,
+) -> (Vec, PyObject) {
+    let mut single_qubit_gates: Vec> = sq_gates
+        .into_iter()
+        .map(|x| x.as_array().to_owned())
+        .collect();
+    let mut diag: Array1 = Array1::ones(2_usize.pow(num_qubits));
+    let num_controls = num_qubits - 1;
+    for dec_step in 0..num_controls {
+        let num_ucgs = 2_usize.pow(dec_step);
+        // The decomposition works recursively and the followign loop goes over the different
+        // UCGates that arise in the decomposition
+        for ucg_index in 0..num_ucgs {
+            let len_ucg = 2_usize.pow(num_controls - dec_step);
+            for i in 0..len_ucg / 2 {
+                let shift = ucg_index * len_ucg;
+                let a = single_qubit_gates[shift + i].view();
+                let b = single_qubit_gates[shift + len_ucg / 2 + i].view();
+                // Apply the decomposition for UCGates given in equation (3) in
+                // https://arxiv.org/pdf/quant-ph/0410066.pdf
+                // to demultiplex one control of all the num_ucgs uniformly-controlled gates
+                // with log2(len_ucg) uniform controls
+                let [v, u, r] = demultiplex_single_uc(a, b);
+                // replace the single-qubit gates with v,u (the already existing ones
+                // are not needed any more)
+                single_qubit_gates[shift + i] = v;
+                single_qubit_gates[shift + len_ucg / 2 + i] = u;
+                // Now we decompose the gates D as described in Figure 4 in
+                // https://arxiv.org/pdf/quant-ph/0410066.pdf and merge some of the gates
+                // into the UCGates and the diagonal at the end of the circuit
+
+                // Remark: The Rz(pi/2) rotation acting on the target qubit and the Hadamard
+                // gates arising in the decomposition of D are ignored for the moment (they will
+                // be added together with the C-NOT gates at the end of the decomposition
+                // (in the method dec_ucg()))
+                let r_conj_t = r.mapv(|x| x.conj()).t().to_owned();
+                if ucg_index < num_ucgs - 1 {
+                    // Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and
+                    // merge the UC-Rz rotation with the following UCGate,
+                    // which hasn't been decomposed yet
+                    let k = shift + len_ucg + i;
+
+                    single_qubit_gates[k] = single_qubit_gates[k].dot(&r_conj_t);
+                    single_qubit_gates[k].mapv_inplace(|x| x * RZ_PI2_00);
+                    let k = k + len_ucg / 2;
+                    single_qubit_gates[k] = single_qubit_gates[k].dot(&r);
+                    single_qubit_gates[k].mapv_inplace(|x| x * RZ_PI2_11);
+                } else {
+                    // Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and merge
+                    // the trailing UC-Rz rotation into a diagonal gate at the end of the circuit
+                    for ucg_index_2 in 0..num_ucgs {
+                        let shift_2 = ucg_index_2 * len_ucg;
+                        let k = 2 * (i + shift_2);
+                        diag[k] *= r_conj_t[[0, 0]] * RZ_PI2_00;
+                        diag[k + 1] *= r_conj_t[[1, 1]] * RZ_PI2_00;
+                        let k = len_ucg + k;
+                        diag[k] *= r[[0, 0]] * RZ_PI2_11;
+                        diag[k + 1] *= r[[1, 1]] * RZ_PI2_11;
+                    }
+                }
+            }
+        }
+    }
+    (
+        single_qubit_gates
+            .into_iter()
+            .map(|x| x.into_pyarray_bound(py).into())
+            .collect(),
+        diag.into_pyarray_bound(py).into(),
+    )
+}
+
+#[pymodule]
+pub fn uc_gate(m: &Bound) -> PyResult<()> {
+    m.add_wrapped(wrap_pyfunction!(dec_ucg_help))?;
+    Ok(())
+}
diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs
index b7c89872bf8d..a21b1307a88f 100644
--- a/crates/pyext/src/lib.rs
+++ b/crates/pyext/src/lib.rs
@@ -15,10 +15,11 @@ use pyo3::wrap_pymodule;
 
 use qiskit_accelerate::{
     convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout,
-    error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, nlayout::nlayout,
-    optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, results::results,
-    sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op,
-    stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, utils::utils,
+    error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer,
+    isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates,
+    pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val,
+    sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap,
+    two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils,
     vf2_layout::vf2_layout,
 };
 
@@ -31,6 +32,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> {
     m.add_wrapped(wrap_pymodule!(dense_layout))?;
     m.add_wrapped(wrap_pymodule!(error_map))?;
     m.add_wrapped(wrap_pymodule!(euler_one_qubit_decomposer))?;
+    m.add_wrapped(wrap_pymodule!(isometry))?;
     m.add_wrapped(wrap_pymodule!(nlayout))?;
     m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?;
     m.add_wrapped(wrap_pymodule!(pauli_expval))?;
@@ -40,6 +42,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> {
     m.add_wrapped(wrap_pymodule!(sparse_pauli_op))?;
     m.add_wrapped(wrap_pymodule!(stochastic_swap))?;
     m.add_wrapped(wrap_pymodule!(two_qubit_decompose))?;
+    m.add_wrapped(wrap_pymodule!(uc_gate))?;
     m.add_wrapped(wrap_pymodule!(utils))?;
     m.add_wrapped(wrap_pymodule!(vf2_layout))?;
     Ok(())
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index 590a5698a77d..e4fbc1729e53 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -65,6 +65,8 @@
 )
 sys.modules["qiskit._accelerate.dense_layout"] = qiskit._accelerate.dense_layout
 sys.modules["qiskit._accelerate.error_map"] = qiskit._accelerate.error_map
+sys.modules["qiskit._accelerate.isometry"] = qiskit._accelerate.isometry
+sys.modules["qiskit._accelerate.uc_gate"] = qiskit._accelerate.uc_gate
 sys.modules["qiskit._accelerate.euler_one_qubit_decomposer"] = (
     qiskit._accelerate.euler_one_qubit_decomposer
 )
diff --git a/qiskit/circuit/library/generalized_gates/isometry.py b/qiskit/circuit/library/generalized_gates/isometry.py
index 1294feb26342..c180e7a14484 100644
--- a/qiskit/circuit/library/generalized_gates/isometry.py
+++ b/qiskit/circuit/library/generalized_gates/isometry.py
@@ -21,7 +21,6 @@
 
 from __future__ import annotations
 
-import itertools
 import math
 import numpy as np
 from qiskit.circuit.exceptions import CircuitError
@@ -30,6 +29,7 @@
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.exceptions import QiskitError
 from qiskit.quantum_info.operators.predicates import is_isometry
+from qiskit._accelerate import isometry as isometry_rs
 
 from .diagonal import Diagonal
 from .uc import UCGate
@@ -157,12 +157,16 @@ def _gates_to_uncompute(self):
         # correspond to the firstfew columns of the identity matrix up to diag, and hence we only
         # have to save a list containing them.
         for column_index in range(2**m):
-            self._decompose_column(circuit, q, diag, remaining_isometry, column_index)
+            remaining_isometry, diag = self._decompose_column(
+                circuit, q, diag, remaining_isometry, column_index
+            )
             # extract phase of the state that was sent to the basis state ket(column_index)
             diag.append(remaining_isometry[column_index, 0])
             # remove first column (which is now stored in diag)
             remaining_isometry = remaining_isometry[:, 1:]
-        if len(diag) > 1 and not _diag_is_identity_up_to_global_phase(diag, self._epsilon):
+        if len(diag) > 1 and not isometry_rs.diag_is_identity_up_to_global_phase(
+            diag, self._epsilon
+        ):
             diagonal = Diagonal(np.conj(diag))
             circuit.append(diagonal, q_input)
         return circuit
@@ -173,7 +177,10 @@ def _decompose_column(self, circuit, q, diag, remaining_isometry, column_index):
         """
         n = int(math.log2(self.iso_data.shape[0]))
         for s in range(n):
-            self._disentangle(circuit, q, diag, remaining_isometry, column_index, s)
+            remaining_isometry, diag = self._disentangle(
+                circuit, q, diag, remaining_isometry, column_index, s
+            )
+        return remaining_isometry, diag
 
     def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s):
         """
@@ -189,13 +196,19 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s):
         n = int(math.log2(self.iso_data.shape[0]))
 
         # MCG to set one entry to zero (preparation for disentangling with UCGate):
-        index1 = 2 * _a(k, s + 1) * 2**s + _b(k, s + 1)
-        index2 = (2 * _a(k, s + 1) + 1) * 2**s + _b(k, s + 1)
+        index1 = 2 * isometry_rs.a(k, s + 1) * 2**s + isometry_rs.b(k, s + 1)
+        index2 = (2 * isometry_rs.a(k, s + 1) + 1) * 2**s + isometry_rs.b(k, s + 1)
         target_label = n - s - 1
         # Check if a MCG is required
-        if _k_s(k, s) == 0 and _b(k, s + 1) != 0 and np.abs(v[index2, k_prime]) > self._epsilon:
+        if (
+            isometry_rs.k_s(k, s) == 0
+            and isometry_rs.b(k, s + 1) != 0
+            and np.abs(v[index2, k_prime]) > self._epsilon
+        ):
             # Find the MCG, decompose it and apply it to the remaining isometry
-            gate = _reverse_qubit_state([v[index1, k_prime], v[index2, k_prime]], 0, self._epsilon)
+            gate = isometry_rs.reverse_qubit_state(
+                [v[index1, k_prime], v[index2, k_prime]], 0, self._epsilon
+            )
             control_labels = [
                 i
                 for i, x in enumerate(_get_binary_rep_as_list(k, n))
@@ -205,57 +218,49 @@ def _disentangle(self, circuit, q, diag, remaining_isometry, column_index, s):
                 circuit, q, gate, control_labels, target_label
             )
             # apply the MCG to the remaining isometry
-            _apply_multi_controlled_gate(v, control_labels, target_label, gate)
+            v = isometry_rs.apply_multi_controlled_gate(v, control_labels, target_label, gate)
             # correct for the implementation "up to diagonal"
-            diag_mcg_inverse = np.conj(diagonal_mcg).tolist()
-            _apply_diagonal_gate(v, control_labels + [target_label], diag_mcg_inverse)
+            diag_mcg_inverse = np.conj(diagonal_mcg).astype(complex, copy=False)
+            v = isometry_rs.apply_diagonal_gate(
+                v, control_labels + [target_label], diag_mcg_inverse
+            )
             # update the diag according to the applied diagonal gate
-            _apply_diagonal_gate_to_diag(diag, control_labels + [target_label], diag_mcg_inverse, n)
+            diag = isometry_rs.apply_diagonal_gate_to_diag(
+                diag, control_labels + [target_label], diag_mcg_inverse, n
+            )
 
         # UCGate to disentangle a qubit:
         # Find the UCGate, decompose it and apply it to the remaining isometry
         single_qubit_gates = self._find_squs_for_disentangling(v, k, s)
-        if not _ucg_is_identity_up_to_global_phase(single_qubit_gates, self._epsilon):
+        if not isometry_rs.ucg_is_identity_up_to_global_phase(single_qubit_gates, self._epsilon):
             control_labels = list(range(target_label))
             diagonal_ucg = self._append_ucg_up_to_diagonal(
                 circuit, q, single_qubit_gates, control_labels, target_label
             )
             # merge the diagonal into the UCGate for efficient application of both together
-            diagonal_ucg_inverse = np.conj(diagonal_ucg).tolist()
-            single_qubit_gates = _merge_UCGate_and_diag(single_qubit_gates, diagonal_ucg_inverse)
+            diagonal_ucg_inverse = np.conj(diagonal_ucg).astype(complex, copy=False)
+            single_qubit_gates = isometry_rs.merge_ucgate_and_diag(
+                single_qubit_gates, diagonal_ucg_inverse
+            )
             # apply the UCGate (with the merged diagonal gate) to the remaining isometry
-            _apply_ucg(v, len(control_labels), single_qubit_gates)
+            v = isometry_rs.apply_ucg(v, len(control_labels), single_qubit_gates)
             # update the diag according to the applied diagonal gate
-            _apply_diagonal_gate_to_diag(
+            diag = isometry_rs.apply_diagonal_gate_to_diag(
                 diag, control_labels + [target_label], diagonal_ucg_inverse, n
             )
             # # correct for the implementation "up to diagonal"
             # diag_inv = np.conj(diag).tolist()
             # _apply_diagonal_gate(v, control_labels + [target_label], diag_inv)
+        return v, diag
 
     # This method finds the single-qubit gates for a UCGate to disentangle a qubit:
     # we consider the n-qubit state v[:,0] starting with k zeros (in the computational basis).
     # The qubit with label n-s-1 is disentangled into the basis state k_s(k,s).
     def _find_squs_for_disentangling(self, v, k, s):
-        k_prime = 0
-        n = int(math.log2(self.iso_data.shape[0]))
-        if _b(k, s + 1) == 0:
-            i_start = _a(k, s + 1)
-        else:
-            i_start = _a(k, s + 1) + 1
-        id_list = [np.eye(2, 2) for _ in range(i_start)]
-        squs = [
-            _reverse_qubit_state(
-                [
-                    v[2 * i * 2**s + _b(k, s), k_prime],
-                    v[(2 * i + 1) * 2**s + _b(k, s), k_prime],
-                ],
-                _k_s(k, s),
-                self._epsilon,
-            )
-            for i in range(i_start, 2 ** (n - s - 1))
-        ]
-        return id_list + squs
+        res = isometry_rs.find_squs_for_disentangling(
+            v, k, s, self._epsilon, n=int(math.log2(self.iso_data.shape[0]))
+        )
+        return res
 
     # Append a UCGate up to diagonal to the circuit circ.
     def _append_ucg_up_to_diagonal(self, circ, q, single_qubit_gates, control_labels, target_label):
@@ -338,146 +343,6 @@ def inv_gate(self):
         return self._inverse
 
 
-# Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or
-# basis_state=1 respectively
-def _reverse_qubit_state(state, basis_state, epsilon):
-    state = np.array(state)
-    r = np.linalg.norm(state)
-    if r < epsilon:
-        return np.eye(2, 2)
-    if basis_state == 0:
-        m = np.array([[np.conj(state[0]), np.conj(state[1])], [-state[1], state[0]]]) / r
-    else:
-        m = np.array([[-state[1], state[0]], [np.conj(state[0]), np.conj(state[1])]]) / r
-    return m
-
-
-# Methods for applying gates to matrices (should be moved to Qiskit AER)
-
-# Input: matrix m with 2^n rows (and arbitrary many columns). Think of the columns as states
-#  on n qubits. The method applies a uniformly controlled gate (UCGate) to all the columns, where
-#  the UCGate is specified by the inputs k and single_qubit_gates:
-
-#  k =  number of controls. We assume that the controls are on the k most significant qubits
-#       (and the target is on the (k+1)th significant qubit)
-#  single_qubit_gates =     [u_0,...,u_{2^k-1}], where the u_i's are 2*2 unitaries
-#                           (provided as numpy arrays)
-
-# The order of the single-qubit unitaries is such that the first unitary u_0 is applied to the
-# (k+1)th significant qubit if the control qubits are in the state ket(0...00), the gate u_1 is
-# applied if the control qubits are in the state ket(0...01), and so on.
-
-# The input matrix m and the single-qubit gates have to be of dtype=complex.
-
-
-def _apply_ucg(m, k, single_qubit_gates):
-    # ToDo: Improve efficiency by parallelizing the gate application. A generalized version of
-    # ToDo: this method should be implemented by the state vector simulator in Qiskit AER.
-    num_qubits = int(math.log2(m.shape[0]))
-    num_col = m.shape[1]
-    spacing = 2 ** (num_qubits - k - 1)
-    for j in range(2 ** (num_qubits - 1)):
-        i = (j // spacing) * spacing + j
-        gate_index = i // (2 ** (num_qubits - k))
-        for col in range(num_col):
-            m[np.array([i, i + spacing]), np.array([col, col])] = np.ndarray.flatten(
-                single_qubit_gates[gate_index].dot(np.array([[m[i, col]], [m[i + spacing, col]]]))
-            ).tolist()
-    return m
-
-
-# Apply a diagonal gate with diagonal entries liste in diag and acting on qubits with labels
-#  action_qubit_labels to a matrix m.
-# The input matrix m has to be of dtype=complex
-# The qubit labels are such that label 0 corresponds to the most significant qubit, label 1 to
-#  the second most significant qubit, and so on ...
-
-
-def _apply_diagonal_gate(m, action_qubit_labels, diag):
-    # ToDo: Improve efficiency by parallelizing the gate application. A generalized version of
-    # ToDo: this method should be implemented by the state vector simulator in Qiskit AER.
-    num_qubits = int(math.log2(m.shape[0]))
-    num_cols = m.shape[1]
-    basis_states = list(itertools.product([0, 1], repeat=num_qubits))
-    for state in basis_states:
-        state_on_action_qubits = [state[i] for i in action_qubit_labels]
-        diag_index = _bin_to_int(state_on_action_qubits)
-        i = _bin_to_int(state)
-        for j in range(num_cols):
-            m[i, j] = diag[diag_index] * m[i, j]
-    return m
-
-
-# Special case of the method _apply_diagonal_gate, where the input m is a diagonal matrix on the
-# log2(len(m_diagonal)) least significant qubits (this method is more efficient in this case
-# than _apply_diagonal_gate). The input m_diagonal is provided as a list of diagonal entries.
-# The diagonal diag is applied on the qubits with labels listed in action_qubit_labels. The input
-# num_qubits gives the total number of considered qubits (this input is required to interpret the
-# action_qubit_labels in relation to the least significant qubits).
-
-
-def _apply_diagonal_gate_to_diag(m_diagonal, action_qubit_labels, diag, num_qubits):
-    if not m_diagonal:
-        return m_diagonal
-    basis_states = list(itertools.product([0, 1], repeat=num_qubits))
-    for state in basis_states[: len(m_diagonal)]:
-        state_on_action_qubits = [state[i] for i in action_qubit_labels]
-        diag_index = _bin_to_int(state_on_action_qubits)
-        i = _bin_to_int(state)
-        m_diagonal[i] *= diag[diag_index]
-    return m_diagonal
-
-
-# Apply a MC single-qubit gate (given by the 2*2 unitary input: gate) with controlling on
-# the qubits with label control_labels and acting on the qubit with label target_label
-# to a matrix m. The input matrix m and the gate have to be of dtype=complex. The qubit labels are
-# such that label 0 corresponds to the most significant qubit, label 1 to the second most
-# significant qubit, and so on ...
-
-
-def _apply_multi_controlled_gate(m, control_labels, target_label, gate):
-    # ToDo: This method should be integrated into the state vector simulator in Qiskit AER.
-    num_qubits = int(math.log2(m.shape[0]))
-    num_cols = m.shape[1]
-    control_labels.sort()
-    free_qubits = num_qubits - len(control_labels) - 1
-    basis_states_free = list(itertools.product([0, 1], repeat=free_qubits))
-    for state_free in basis_states_free:
-        (e1, e2) = _construct_basis_states(state_free, control_labels, target_label)
-        for i in range(num_cols):
-            m[np.array([e1, e2]), np.array([i, i])] = np.ndarray.flatten(
-                gate.dot(np.array([[m[e1, i]], [m[e2, i]]]))
-            ).tolist()
-    return m
-
-
-# Helper method for _apply_multi_controlled_gate. This constructs the basis states the MG gate
-# is acting on for a specific state state_free of the qubits we neither control nor act on.
-
-
-def _construct_basis_states(state_free, control_labels, target_label):
-    e1 = []
-    e2 = []
-    j = 0
-    for i in range(len(state_free) + len(control_labels) + 1):
-        if i in control_labels:
-            e1.append(1)
-            e2.append(1)
-        elif i == target_label:
-            e1.append(0)
-            e2.append(1)
-        else:
-            e1.append(state_free[j])
-            e2.append(state_free[j])
-            j += 1
-    out1 = _bin_to_int(e1)
-    out2 = _bin_to_int(e2)
-    return out1, out2
-
-
-# Some helper methods:
-
-
 # Get the qubits in the list qubits corresponding to the labels listed in labels. The total number
 # of qubits is given by num_qubits (and determines the convention for the qubit labeling)
 
@@ -496,14 +361,6 @@ def _reverse_qubit_oder(qubits):
 # Convert list of binary digits to integer
 
 
-def _bin_to_int(binary_digits_as_list):
-    return int("".join(str(x) for x in binary_digits_as_list), 2)
-
-
-def _ct(m):
-    return np.transpose(np.conjugate(m))
-
-
 def _get_binary_rep_as_list(n, num_digits):
     binary_string = np.binary_repr(n).zfill(num_digits)
     binary = []
@@ -511,64 +368,3 @@ def _get_binary_rep_as_list(n, num_digits):
         for c in line:
             binary.append(int(c))
     return binary[-num_digits:]
-
-
-# absorb a diagonal gate into a UCGate
-
-
-def _merge_UCGate_and_diag(single_qubit_gates, diag):
-    for i, gate in enumerate(single_qubit_gates):
-        single_qubit_gates[i] = np.array([[diag[2 * i], 0.0], [0.0, diag[2 * i + 1]]]).dot(gate)
-    return single_qubit_gates
-
-
-# Helper variables/functions for the column-by-column decomposition
-
-
-# a(k,s) and b(k,s) are positive integers such that k = a(k,s)2^s + b(k,s)
-# (with the maximal choice of a(k,s))
-
-
-def _a(k, s):
-    return k // 2**s
-
-
-def _b(k, s):
-    return k - (_a(k, s) * 2**s)
-
-
-# given a binary representation of k with binary digits [k_{n-1},..,k_1,k_0],
-# the method k_s(k, s) returns k_s
-
-
-def _k_s(k, s):
-    if k == 0:
-        return 0
-    else:
-        num_digits = s + 1
-        return _get_binary_rep_as_list(k, num_digits)[0]
-
-
-# Check if a gate of a special form is equal to the identity gate up to global phase
-
-
-def _ucg_is_identity_up_to_global_phase(single_qubit_gates, epsilon):
-    if not np.abs(single_qubit_gates[0][0, 0]) < epsilon:
-        global_phase = 1.0 / (single_qubit_gates[0][0, 0])
-    else:
-        return False
-    for gate in single_qubit_gates:
-        if not np.allclose(global_phase * gate, np.eye(2, 2)):
-            return False
-    return True
-
-
-def _diag_is_identity_up_to_global_phase(diag, epsilon):
-    if not np.abs(diag[0]) < epsilon:
-        global_phase = 1.0 / (diag[0])
-    else:
-        return False
-    for d in diag:
-        if not np.abs(global_phase * d - 1) < epsilon:
-            return False
-    return True
diff --git a/qiskit/circuit/library/generalized_gates/uc.py b/qiskit/circuit/library/generalized_gates/uc.py
index 2d650e98466a..f54567123e02 100644
--- a/qiskit/circuit/library/generalized_gates/uc.py
+++ b/qiskit/circuit/library/generalized_gates/uc.py
@@ -21,7 +21,6 @@
 
 from __future__ import annotations
 
-import cmath
 import math
 
 import numpy as np
@@ -33,14 +32,11 @@
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.exceptions import QiskitError
-
-# pylint: disable=cyclic-import
-from qiskit.synthesis.one_qubit.one_qubit_decompose import OneQubitEulerDecomposer
+from qiskit._accelerate import uc_gate
 
 from .diagonal import Diagonal
 
 _EPS = 1e-10  # global variable used to chop very small numbers to zero
-_DECOMPOSER1Q = OneQubitEulerDecomposer("U3")
 
 
 class UCGate(Gate):
@@ -203,99 +199,7 @@ def _dec_ucg_help(self):
         https://arxiv.org/pdf/quant-ph/0410066.pdf.
         """
         single_qubit_gates = [gate.astype(complex) for gate in self.params]
-        diag = np.ones(2**self.num_qubits, dtype=complex)
-        num_contr = self.num_qubits - 1
-        for dec_step in range(num_contr):
-            num_ucgs = 2**dec_step
-            # The decomposition works recursively and the following loop goes over the different
-            # UCGates that arise in the decomposition
-            for ucg_index in range(num_ucgs):
-                len_ucg = 2 ** (num_contr - dec_step)
-                for i in range(int(len_ucg / 2)):
-                    shift = ucg_index * len_ucg
-                    a = single_qubit_gates[shift + i]
-                    b = single_qubit_gates[shift + len_ucg // 2 + i]
-                    # Apply the decomposition for UCGates given in equation (3) in
-                    # https://arxiv.org/pdf/quant-ph/0410066.pdf
-                    # to demultiplex one control of all the num_ucgs uniformly-controlled gates
-                    #  with log2(len_ucg) uniform controls
-                    v, u, r = self._demultiplex_single_uc(a, b)
-                    #  replace the single-qubit gates with v,u (the already existing ones
-                    #  are not needed any more)
-                    single_qubit_gates[shift + i] = v
-                    single_qubit_gates[shift + len_ucg // 2 + i] = u
-                    # Now we decompose the gates D as described in Figure 4  in
-                    # https://arxiv.org/pdf/quant-ph/0410066.pdf and merge some of the gates
-                    # into the UCGates and the diagonal at the end of the circuit
-
-                    # Remark: The Rz(pi/2) rotation acting on the target qubit and the Hadamard
-                    # gates arising in the decomposition of D are ignored for the moment (they will
-                    # be added together with the C-NOT gates at the end of the decomposition
-                    # (in the method dec_ucg()))
-                    if ucg_index < num_ucgs - 1:
-                        # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and
-                        # merge the UC-Rz rotation with the following UCGate,
-                        # which hasn't been decomposed yet.
-                        k = shift + len_ucg + i
-                        single_qubit_gates[k] = single_qubit_gates[k].dot(
-                            UCGate._ct(r)
-                        ) * UCGate._rz(np.pi / 2).item((0, 0))
-                        k = k + len_ucg // 2
-                        single_qubit_gates[k] = single_qubit_gates[k].dot(r) * UCGate._rz(
-                            np.pi / 2
-                        ).item((1, 1))
-                    else:
-                        # Absorb the Rz(pi/2) rotation on the control into the UC-Rz gate and merge
-                        # the trailing UC-Rz rotation into a diagonal gate at the end of the circuit
-                        for ucg_index_2 in range(num_ucgs):
-                            shift_2 = ucg_index_2 * len_ucg
-                            k = 2 * (i + shift_2)
-                            diag[k] = (
-                                diag[k]
-                                * UCGate._ct(r).item((0, 0))
-                                * UCGate._rz(np.pi / 2).item((0, 0))
-                            )
-                            diag[k + 1] = (
-                                diag[k + 1]
-                                * UCGate._ct(r).item((1, 1))
-                                * UCGate._rz(np.pi / 2).item((0, 0))
-                            )
-                            k = len_ucg + k
-                            diag[k] *= r.item((0, 0)) * UCGate._rz(np.pi / 2).item((1, 1))
-                            diag[k + 1] *= r.item((1, 1)) * UCGate._rz(np.pi / 2).item((1, 1))
-        return single_qubit_gates, diag
-
-    def _demultiplex_single_uc(self, a, b):
-        """
-        This method implements the decomposition given in equation (3) in
-        https://arxiv.org/pdf/quant-ph/0410066.pdf.
-        The decomposition is used recursively to decompose uniformly controlled gates.
-        a,b = single qubit unitaries
-        v,u,r = outcome of the decomposition given in the reference mentioned above
-        (see there for the details).
-        """
-        # The notation is chosen as in https://arxiv.org/pdf/quant-ph/0410066.pdf.
-        x = a.dot(UCGate._ct(b))
-        det_x = np.linalg.det(x)
-        x11 = x.item((0, 0)) / cmath.sqrt(det_x)
-        phi = cmath.phase(det_x)
-        r1 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 - cmath.phase(x11)))
-        r2 = cmath.exp(1j / 2 * (np.pi / 2 - phi / 2 + cmath.phase(x11) + np.pi))
-        r = np.array([[r1, 0], [0, r2]], dtype=complex)
-        d, u = np.linalg.eig(r.dot(x).dot(r))
-        # If d is not equal to diag(i,-i), then we put it into this "standard" form
-        # (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging
-        # the eigenvalues and eigenvectors.
-        if abs(d[0] + 1j) < _EPS:
-            d = np.flip(d, 0)
-            u = np.flip(u, 1)
-        d = np.diag(np.sqrt(d))
-        v = d.dot(UCGate._ct(u)).dot(UCGate._ct(r)).dot(b)
-        return v, u, r
-
-    @staticmethod
-    def _ct(m):
-        return np.transpose(np.conjugate(m))
+        return uc_gate.dec_ucg_help(single_qubit_gates, self.num_qubits)
 
     @staticmethod
     def _rz(alpha):

From f2b874b83c30df6bd08aa2c13399f9f8020b7a9a Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Tue, 30 Apr 2024 19:24:28 -0400
Subject: [PATCH 042/179] Promote arm64 macOS to tier 1 (#12102)

* Promote arm64 macOS to tier 1

Github recently added a new macOS runner that is using the m1 CPU that
is usable for open source projects. [1] Previously Qiskit had support
for arm64 macOS at tier 3 because we were only able to cross compile
for the platform and not test the binaries. Now that we can run CI jobs
on the platform we're able to run both unit tests and test our binaries
on release. This commit adds a new set of test jobs and wheel builds
that use the macos-14 runner that mirrors the existing x86_64 macOS
jobs we have. This brings arm64 macOS to the same support level as
x86_64. THe only difference here is that azure pipelines doesn't have
arm64 macOS support so the test job will run in github actions instead.

[1] https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/

* Update test job

* Fix syntax error

* Use a string for python version in test job

* Disable fail-fast on test job

* DNM: Test wheel builds

* Skip universal builds

* Force system python to arm64 on macOS arm64 bwheel builds

* Correctly skip universal builds

* Skip python 3.8 arm64

* Add back cross build job just for python 3.8

* Add numpy env details to arm64 test job

* Use MSRV for one of the test jobs

* Fix build_pgo.sh script when running on arm64 mac

* Revert "DNM: Test wheel builds"

This reverts commit 97eaa6fee84d8209e530e59017478c78bce1a868.

* Rename macos arm py38 cross-build job
---
 .github/workflows/tests.yml                   | 44 +++++++++++++++++++
 .github/workflows/wheels.yml                  | 43 +++++++++++-------
 pyproject.toml                                |  2 +-
 .../macos-arm64-tier-1-c5030f009be6adcb.yaml  | 11 +++++
 tools/build_pgo.sh                            |  8 +++-
 5 files changed, 89 insertions(+), 19 deletions(-)
 create mode 100644 .github/workflows/tests.yml
 create mode 100644 releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000000..0d04e21a1696
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,44 @@
+---
+name: Tests
+on:
+  push:
+    branches: [ main, 'stable/*' ]
+  pull_request:
+    branches: [ main, 'stable/*' ]
+
+concurrency:
+  group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}
+  cancel-in-progress: true
+jobs:
+  tests:
+    if: github.repository_owner == 'Qiskit'
+    name: macOS-arm64-tests-Python-${{ matrix.python-version }}
+    runs-on: macOS-14
+    strategy:
+      fail-fast: false
+      matrix:
+        # Normally we test min and max version but we can't run python 3.8 or
+        # 3.9 on arm64 until actions/setup-python#808 is resolved
+        python-version: ["3.10", "3.12"]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Install Rust toolchain
+        uses: dtolnay/rust-toolchain@1.70
+        if: matrix.python-version == '3.10'
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+          architecture: arm64
+      - name: 'Install dependencies'
+        run: |
+          python -m pip install -U -r requirements.txt -c constraints.txt
+          python -m pip install -U -r requirements-dev.txt -c constraints.txt
+          python -m pip install -c constraints.txt -e .
+      - name: 'Install optionals'
+        run: |
+          python -m pip install -r requirements-optional.txt -c constraints.txt
+          python tools/report_numpy_state.py
+        if: matrix.python-version == '3.10'
+      - name: 'Run tests'
+        run: stestr run
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 67104433a3dc..2cd3c8ac0a39 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -12,13 +12,20 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, macos-11, windows-latest]
+        os: [ubuntu-latest, macos-11, windows-latest, macos-14]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
         name: Install Python
         with:
           python-version: '3.10'
+        if: matrix.os != 'macos-14'
+      - uses: actions/setup-python@v5
+        name: Install Python
+        with:
+          python-version: '3.10'
+          architecture: arm64
+        if: matrix.os == 'macos-14'
       - uses: dtolnay/rust-toolchain@stable
         with:
           components: llvm-tools-preview
@@ -34,13 +41,13 @@ jobs:
         with:
           path: ./wheelhouse/*.whl
           name: wheels-${{ matrix.os }}
-  build_wheels_32bit:
-    name: Build wheels 32bit
+  build_wheels_macos_arm_py38:
+    name: Build wheels on macOS arm
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, windows-latest]
+        os: [macos-11]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
@@ -53,19 +60,23 @@ jobs:
       - name: Build wheels
         uses: pypa/cibuildwheel@v2.17.0
         env:
-          CIBW_SKIP: 'pp* cp36-* cp37-* *musllinux* *amd64 *x86_64'
+          CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin
+          CIBW_BUILD: cp38-macosx_universal2 cp38-macosx_arm64
+          CIBW_ARCHS_MACOS: arm64 universal2
+          CIBW_ENVIRONMENT: >-
+            CARGO_BUILD_TARGET="aarch64-apple-darwin"
+            PYO3_CROSS_LIB_DIR="/Library/Frameworks/Python.framework/Versions/$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')/lib/python$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')"
       - uses: actions/upload-artifact@v4
         with:
           path: ./wheelhouse/*.whl
-          name: wheels-${{ matrix.os }}-32
-  build_wheels_macos_arm:
-    name: Build wheels on macOS arm
+          name: wheels-${{ matrix.os }}-arm
+  build_wheels_32bit:
+    name: Build wheels 32bit
     runs-on: ${{ matrix.os }}
-    environment: release
     strategy:
       fail-fast: false
       matrix:
-        os: [macos-11]
+        os: [ubuntu-latest, windows-latest]
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-python@v5
@@ -73,25 +84,23 @@ jobs:
         with:
           python-version: '3.10'
       - uses: dtolnay/rust-toolchain@stable
+        with:
+          components: llvm-tools-preview
       - name: Build wheels
         uses: pypa/cibuildwheel@v2.17.0
         env:
-          CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin
-          CIBW_ARCHS_MACOS: arm64 universal2
-          CIBW_ENVIRONMENT: >-
-            CARGO_BUILD_TARGET="aarch64-apple-darwin"
-            PYO3_CROSS_LIB_DIR="/Library/Frameworks/Python.framework/Versions/$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')/lib/python$(python -c 'import sys; print(str(sys.version_info[0])+"."+str(sys.version_info[1]))')"
+          CIBW_SKIP: 'pp* cp36-* cp37-* *musllinux* *amd64 *x86_64'
       - uses: actions/upload-artifact@v4
         with:
           path: ./wheelhouse/*.whl
-          name: wheels-${{ matrix.os }}-arm
+          name: wheels-${{ matrix.os }}-32
   upload_shared_wheels:
     name: Upload shared build wheels
     runs-on: ubuntu-latest
     environment: release
     permissions:
       id-token: write
-    needs: ["build_wheels", "build_wheels_macos_arm", "build_wheels_32bit"]
+    needs: ["build_wheels", "build_wheels_32bit", "build_wheels_macos_arm_py38"]
     steps:
       - uses: actions/download-artifact@v4
         with:
diff --git a/pyproject.toml b/pyproject.toml
index 975bd377760c..97ccde21d1b7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -136,7 +136,7 @@ target-version = ['py38', 'py39', 'py310', 'py311']
 [tool.cibuildwheel]
 manylinux-x86_64-image = "manylinux2014"
 manylinux-i686-image = "manylinux2014"
-skip = "pp* cp36-* cp37-* *musllinux* *win32 *i686"
+skip = "pp* cp36-* cp37-* *musllinux* *win32 *i686 cp38-macosx_arm64"
 test-skip = "*win32 *linux_i686"
 test-command = "python {project}/examples/python/stochastic_swap.py"
 # We need to use pre-built versions of Numpy and Scipy in the tests; they have a
diff --git a/releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml b/releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml
new file mode 100644
index 000000000000..b59f5b9844c9
--- /dev/null
+++ b/releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml
@@ -0,0 +1,11 @@
+---
+other:
+  - |
+    Support for the arm64 macOS platform has been promoted from Tier 3
+    to Tier 1. Previously the platform was at Tier 3 because there was
+    no available CI environment for testing Qiskit on the platform. Now
+    that Github has made an arm64 macOS environment available to open source
+    projects [#]_ we're testing the platform along with the other Tier 1
+    supported platforms.
+
+    .. [#] https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/
diff --git a/tools/build_pgo.sh b/tools/build_pgo.sh
index d0e88bf6f745..8553691bdfe4 100755
--- a/tools/build_pgo.sh
+++ b/tools/build_pgo.sh
@@ -17,6 +17,12 @@ else
     source build_pgo/bin/activate
 fi
 
+arch=`uname -m`
+# Handle macOS calling the architecture arm64 and rust calling it aarch64
+if [[ $arch == "arm64" ]]; then
+    arch="aarch64"
+fi
+
 # Build with instrumentation
 pip install -U -c constraints.txt setuptools-rust wheel setuptools
 RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" pip install --prefer-binary -c constraints.txt -r requirements-dev.txt -e .
@@ -29,4 +35,4 @@ python tools/pgo_scripts/test_utility_scale.py
 
 deactivate
 
-${HOME}/.rustup/toolchains/*x86_64*/lib/rustlib/x86_64*/bin/llvm-profdata merge -o $merged_path /tmp/pgo-data
+${HOME}/.rustup/toolchains/*$arch*/lib/rustlib/$arch*/bin/llvm-profdata merge -o $merged_path /tmp/pgo-data

From c6ba3a8dbb1ed5c793fea307c56290edbcb0d590 Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Wed, 1 May 2024 06:04:37 -0400
Subject: [PATCH 043/179] document entanglement="pairwise" for EfficientSU2
 (#12314)

---
 qiskit/circuit/library/n_local/efficient_su2.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/qiskit/circuit/library/n_local/efficient_su2.py b/qiskit/circuit/library/n_local/efficient_su2.py
index fc72a2a6c530..e27fe407e188 100644
--- a/qiskit/circuit/library/n_local/efficient_su2.py
+++ b/qiskit/circuit/library/n_local/efficient_su2.py
@@ -110,11 +110,11 @@ def __init__(
                 If only one gate is provided, the same gate is applied to each qubit.
                 If a list of gates is provided, all gates are applied to each qubit in the provided
                 order.
-            entanglement: Specifies the entanglement structure. Can be a string ('full', 'linear'
-                , 'reverse_linear', 'circular' or 'sca'), a list of integer-pairs specifying the indices
-                of qubits entangled with one another, or a callable returning such a list provided with
-                the index of the entanglement layer.
-                Default to 'reverse_linear' entanglement.
+            entanglement: Specifies the entanglement structure. Can be a string
+                ('full', 'linear', 'reverse_linear', 'pairwise', 'circular', or 'sca'),
+                a list of integer-pairs specifying the indices of qubits entangled with one another,
+                or a callable returning such a list provided with the index of the entanglement layer.
+                Defaults to 'reverse_linear' entanglement.
                 Note that 'reverse_linear' entanglement provides the same unitary as 'full'
                 with fewer entangling gates.
                 See the Examples section of :class:`~qiskit.circuit.library.TwoLocal` for more

From a4f272f131d1c966c320ce7e8752c3d8ab9aafb0 Mon Sep 17 00:00:00 2001
From: Alexander Ivrii 
Date: Wed, 1 May 2024 13:35:36 +0300
Subject: [PATCH 044/179] typos (#12316)

---
 .../transpiler/passes/synthesis/high_level_synthesis.py   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
index 3d3e2a6851af..e6442ecfb442 100644
--- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
@@ -732,9 +732,9 @@ class KMSSynthesisLinearFunction(HighLevelSynthesisPlugin):
 
     * use_inverted: Indicates whether to run the algorithm on the inverse matrix
         and to invert the synthesized circuit.
-        In certain cases this provides a better decomposition then the direct approach.
+        In certain cases this provides a better decomposition than the direct approach.
     * use_transposed: Indicates whether to run the algorithm on the transposed matrix
-        and to invert the order oF CX gates in the synthesized circuit.
+        and to invert the order of CX gates in the synthesized circuit.
         In certain cases this provides a better decomposition than the direct approach.
 
     """
@@ -778,9 +778,9 @@ class PMHSynthesisLinearFunction(HighLevelSynthesisPlugin):
     * section size: The size of each section used in the Patel–Markov–Hayes algorithm [1].
     * use_inverted: Indicates whether to run the algorithm on the inverse matrix
         and to invert the synthesized circuit.
-        In certain cases this provides a better decomposition then the direct approach.
+        In certain cases this provides a better decomposition than the direct approach.
     * use_transposed: Indicates whether to run the algorithm on the transposed matrix
-        and to invert the order oF CX gates in the synthesized circuit.
+        and to invert the order of CX gates in the synthesized circuit.
         In certain cases this provides a better decomposition than the direct approach.
 
     References:

From a65c9e628c3932b5984c13acb0a35d0645567901 Mon Sep 17 00:00:00 2001
From: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
Date: Wed, 1 May 2024 13:35:51 +0200
Subject: [PATCH 045/179] Fixing Operator.from_circuit for circuits with final
 layouts and a non-trivial initial layout (#12057)

* reno, format, test changes

* fix Operator.from_circuit and add failing test case

* Update test_operator.py

* Update operator.py

* Update releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml

Co-authored-by: Matthew Treinish 

* selective merge of 11399

* reimplementing from_circuit

---------

Co-authored-by: Matthew Treinish 
Co-authored-by: AlexanderIvrii 
---
 qiskit/quantum_info/operators/operator.py     | 49 +++++++++++-------
 ...-from-circuit-bugfix-5dab5993526a2b0a.yaml |  7 +++
 .../quantum_info/operators/test_operator.py   | 51 +++++++++++++++++--
 3 files changed, 86 insertions(+), 21 deletions(-)
 create mode 100644 releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml

diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
index d119a3812493..41eac3563576 100644
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -82,6 +82,9 @@ def __init__(
             a Numpy array of shape (2**N, 2**N) qubit systems will be used. If
             the input operator is not an N-qubit operator, it will assign a
             single subsystem with dimension specified by the shape of the input.
+            Note that two operators initialized via this method are only considered equivalent if they
+            match up to their canonical qubit order (or: permutation). See :meth:`.Operator.from_circuit`
+            to specify a different qubit permutation.
         """
         op_shape = None
         if isinstance(data, (list, np.ndarray)):
@@ -391,8 +394,7 @@ def from_circuit(
         Returns:
             Operator: An operator representing the input circuit
         """
-        dimension = 2**circuit.num_qubits
-        op = cls(np.eye(dimension))
+
         if layout is None:
             if not ignore_set_layout:
                 layout = getattr(circuit, "_layout", None)
@@ -403,27 +405,38 @@ def from_circuit(
                 initial_layout=layout,
                 input_qubit_mapping={qubit: index for index, qubit in enumerate(circuit.qubits)},
             )
+
+        initial_layout = layout.initial_layout if layout is not None else None
+
         if final_layout is None:
             if not ignore_set_layout and layout is not None:
                 final_layout = getattr(layout, "final_layout", None)
 
-        qargs = None
-        # If there was a layout specified (either from the circuit
-        # or via user input) use that to set qargs to permute qubits
-        # based on that layout
-        if layout is not None:
-            physical_to_virtual = layout.initial_layout.get_physical_bits()
-            qargs = [
-                layout.input_qubit_mapping[physical_to_virtual[physical_bit]]
-                for physical_bit in range(len(physical_to_virtual))
-            ]
-        # Convert circuit to an instruction
-        instruction = circuit.to_instruction()
-        op._append_instruction(instruction, qargs=qargs)
-        # If final layout is set permute output indices based on layout
+        from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern
+
+        if initial_layout is not None:
+            input_qubits = [None] * len(layout.input_qubit_mapping)
+            for q, p in layout.input_qubit_mapping.items():
+                input_qubits[p] = q
+
+            initial_permutation = initial_layout.to_permutation(input_qubits)
+            initial_permutation_inverse = _inverse_pattern(initial_permutation)
+
         if final_layout is not None:
-            perm_pattern = [final_layout._v2p[v] for v in circuit.qubits]
-            op = op.apply_permutation(perm_pattern, front=False)
+            final_permutation = final_layout.to_permutation(circuit.qubits)
+            final_permutation_inverse = _inverse_pattern(final_permutation)
+
+        op = Operator(circuit)
+
+        if initial_layout:
+            op = op.apply_permutation(initial_permutation, True)
+
+        if final_layout:
+            op = op.apply_permutation(final_permutation_inverse, False)
+
+        if initial_layout:
+            op = op.apply_permutation(initial_permutation_inverse, False)
+
         return op
 
     def is_unitary(self, atol=None, rtol=None):
diff --git a/releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml b/releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml
new file mode 100644
index 000000000000..759f023efc87
--- /dev/null
+++ b/releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed an issue with the :meth:`.Operator.from_circuit` constructor method where it would incorrectly
+    interpret the final layout permutation resulting in an invalid `Operator` being constructed.
+    Previously, the final layout was processed without regards for the initial layout, i.e. the
+    initialization was incorrect for all quantum circuits that have a non-trivial initial layout.
diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py
index fc824643a0ba..d653d6182017 100644
--- a/test/python/quantum_info/operators/test_operator.py
+++ b/test/python/quantum_info/operators/test_operator.py
@@ -17,6 +17,7 @@
 import unittest
 import logging
 import copy
+
 from test import combine
 import numpy as np
 from ddt import ddt
@@ -26,6 +27,7 @@
 from qiskit import QiskitError
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit.circuit.library import HGate, CHGate, CXGate, QFT
+from qiskit.transpiler import CouplingMap
 from qiskit.transpiler.layout import Layout, TranspileLayout
 from qiskit.quantum_info.operators import Operator, ScalarOp
 from qiskit.quantum_info.operators.predicates import matrix_equal
@@ -735,6 +737,28 @@ def test_from_circuit_constructor_no_layout(self):
         global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True)
         self.assertTrue(global_phase_equivalent)
 
+    def test_from_circuit_initial_layout_final_layout(self):
+        """Test initialization from a circuit with a non-trivial initial_layout and final_layout as given
+        by a transpiled circuit."""
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.cx(2, 1)
+        qc.cx(1, 2)
+        qc.cx(1, 0)
+        qc.cx(1, 3)
+        qc.cx(1, 4)
+        qc.h(2)
+
+        qc_transpiled = transpile(
+            qc,
+            coupling_map=CouplingMap.from_line(5),
+            initial_layout=[2, 3, 4, 0, 1],
+            optimization_level=1,
+            seed_transpiler=17,
+        )
+
+        self.assertTrue(Operator.from_circuit(qc_transpiled).equiv(qc))
+
     def test_from_circuit_constructor_reverse_embedded_layout(self):
         """Test initialization from a circuit with an embedded reverse layout."""
         # Test tensor product of 1-qubit gates
@@ -817,7 +841,7 @@ def test_from_circuit_constructor_reverse_embedded_layout_and_final_layout(self)
         circuit._layout = TranspileLayout(
             Layout({circuit.qubits[2]: 0, circuit.qubits[1]: 1, circuit.qubits[0]: 2}),
             {qubit: index for index, qubit in enumerate(circuit.qubits)},
-            Layout({circuit.qubits[0]: 1, circuit.qubits[1]: 2, circuit.qubits[2]: 0}),
+            Layout({circuit.qubits[0]: 2, circuit.qubits[1]: 0, circuit.qubits[2]: 1}),
         )
         circuit.swap(0, 1)
         circuit.swap(1, 2)
@@ -839,7 +863,7 @@ def test_from_circuit_constructor_reverse_embedded_layout_and_manual_final_layou
             Layout({circuit.qubits[2]: 0, circuit.qubits[1]: 1, circuit.qubits[0]: 2}),
             {qubit: index for index, qubit in enumerate(circuit.qubits)},
         )
-        final_layout = Layout({circuit.qubits[0]: 1, circuit.qubits[1]: 2, circuit.qubits[2]: 0})
+        final_layout = Layout({circuit.qubits[0]: 2, circuit.qubits[1]: 0, circuit.qubits[2]: 1})
         circuit.swap(0, 1)
         circuit.swap(1, 2)
         op = Operator.from_circuit(circuit, final_layout=final_layout)
@@ -966,7 +990,7 @@ def test_from_circuit_constructor_empty_layout(self):
         circuit.h(0)
         circuit.cx(0, 1)
         layout = Layout()
-        with self.assertRaises(IndexError):
+        with self.assertRaises(KeyError):
             Operator.from_circuit(circuit, layout=layout)
 
     def test_compose_scalar(self):
@@ -1078,6 +1102,27 @@ def test_from_circuit_mixed_reg_loose_bits_transpiled(self):
         result = Operator.from_circuit(tqc)
         self.assertTrue(Operator(circuit).equiv(result))
 
+    def test_from_circuit_into_larger_map(self):
+        """Test from_circuit method when the number of physical
+        qubits is larger than the number of original virtual qubits."""
+
+        # original circuit on 3 qubits
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.cx(1, 2)
+
+        # transpile into 5-qubits
+        tqc = transpile(qc, coupling_map=CouplingMap.from_line(5), initial_layout=[0, 2, 4])
+
+        # qc expanded with ancilla qubits
+        expected = QuantumCircuit(5)
+        expected.h(0)
+        expected.cx(0, 1)
+        expected.cx(1, 2)
+
+        self.assertEqual(Operator.from_circuit(tqc), Operator(expected))
+
     def test_apply_permutation_back(self):
         """Test applying permutation to the operator,
         where the operator is applied first and the permutation second."""

From cd03721e5f652c79088fb1a0495c296daa05935d Mon Sep 17 00:00:00 2001
From: Alexander Ivrii 
Date: Wed, 1 May 2024 15:56:29 +0300
Subject: [PATCH 046/179] HLSConfig option to run multiple plugins and to
 choose the best decomposition (#12108)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix docstring

* import

* exposing additional plugin arguments

* tests

* lint

* release notes

* HLS config option to run all specified plugins + tests

* lint"

* removing todo

* release notes

* fixes

---------

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
---
 .../passes/synthesis/high_level_synthesis.py  | 49 ++++++++++---
 ...n-all-plugins-option-ba8806a269e5713c.yaml | 51 +++++++++++++
 .../transpiler/test_high_level_synthesis.py   | 73 +++++++++++++++++++
 3 files changed, 163 insertions(+), 10 deletions(-)
 create mode 100644 releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml

diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
index e6442ecfb442..fd21ae6a75fc 100644
--- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
@@ -133,7 +133,7 @@
    TokenSwapperSynthesisPermutation
 """
 
-from typing import Optional, Union, List, Tuple
+from typing import Optional, Union, List, Tuple, Callable
 
 import numpy as np
 import rustworkx as rx
@@ -227,16 +227,34 @@ class HLSConfig:
     :ref:`using-high-level-synthesis-plugins`.
     """
 
-    def __init__(self, use_default_on_unspecified=True, **kwargs):
+    def __init__(
+        self,
+        use_default_on_unspecified: bool = True,
+        plugin_selection: str = "sequential",
+        plugin_evaluation_fn: Optional[Callable[[QuantumCircuit], int]] = None,
+        **kwargs,
+    ):
         """Creates a high-level-synthesis config.
 
         Args:
-            use_default_on_unspecified (bool): if True, every higher-level-object without an
+            use_default_on_unspecified: if True, every higher-level-object without an
                 explicitly specified list of methods will be synthesized using the "default"
                 algorithm if it exists.
+            plugin_selection: if set to ``"sequential"`` (default), for every higher-level-object
+                the synthesis pass will consider the specified methods sequentially, stopping
+                at the first method that is able to synthesize the object. If set to ``"all"``,
+                all the specified methods will be considered, and the best synthesized circuit,
+                according to ``plugin_evaluation_fn`` will be chosen.
+            plugin_evaluation_fn: a callable that evaluates the quality of the synthesized
+                quantum circuit; a smaller value means a better circuit. If ``None``, the
+                quality of the circuit its size (i.e. the number of gates that it contains).
             kwargs: a dictionary mapping higher-level-objects to lists of synthesis methods.
         """
         self.use_default_on_unspecified = use_default_on_unspecified
+        self.plugin_selection = plugin_selection
+        self.plugin_evaluation_fn = (
+            plugin_evaluation_fn if plugin_evaluation_fn is not None else lambda qc: qc.size()
+        )
         self.methods = {}
 
         for key, value in kwargs.items():
@@ -248,9 +266,6 @@ def set_methods(self, hls_name, hls_methods):
         self.methods[hls_name] = hls_methods
 
 
-# ToDo: Do we have a way to specify optimization criteria (e.g., 2q gate count vs. depth)?
-
-
 class HighLevelSynthesis(TransformationPass):
     """Synthesize higher-level objects and unroll custom definitions.
 
@@ -500,6 +515,9 @@ def _synthesize_op_using_plugins(
         else:
             methods = []
 
+        best_decomposition = None
+        best_score = np.inf
+
         for method in methods:
             # There are two ways to specify a synthesis method. The more explicit
             # way is to specify it as a tuple consisting of a synthesis algorithm and a
@@ -538,11 +556,22 @@ def _synthesize_op_using_plugins(
             )
 
             # The synthesis methods that are not suited for the given higher-level-object
-            # will return None, in which case the next method in the list will be used.
+            # will return None.
             if decomposition is not None:
-                return decomposition
-
-        return None
+                if self.hls_config.plugin_selection == "sequential":
+                    # In the "sequential" mode the first successful decomposition is
+                    # returned.
+                    best_decomposition = decomposition
+                    break
+
+                # In the "run everything" mode we update the best decomposition
+                # discovered
+                current_score = self.hls_config.plugin_evaluation_fn(decomposition)
+                if current_score < best_score:
+                    best_decomposition = decomposition
+                    best_score = current_score
+
+        return best_decomposition
 
     def _synthesize_annotated_op(self, op: Operation) -> Union[Operation, None]:
         """
diff --git a/releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml b/releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml
new file mode 100644
index 000000000000..2ab34c61fb35
--- /dev/null
+++ b/releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml
@@ -0,0 +1,51 @@
+---
+features:
+  - |
+    The :class:`~.HLSConfig` now has two additional optional arguments. The argument
+    ``plugin_selection`` can be set either to ``"sequential"`` or to ``"all"``.
+    If set to "sequential" (default), for every higher-level-object
+    the :class:`~qiskit.transpiler.passes.HighLevelSynthesis` pass will consider the
+    specified methods sequentially, in the order they appear in the list, stopping
+    at the first method that is able to synthesize the object. If set to "all",
+    all the specified methods will be considered, and the best synthesized circuit,
+    according to ``plugin_evaluation_fn`` will be chosen. The argument
+    ``plugin_evaluation_fn`` is an optional callable that evaluates the quality of
+    the synthesized quantum circuit; a smaller value means a better circuit. When
+    set to ``None``, the quality of the circuit is its size (i.e. the number of gates
+    that it contains).
+
+    The following example illustrates the new functionality::
+
+        from qiskit import QuantumCircuit
+        from qiskit.circuit.library import LinearFunction
+        from qiskit.synthesis.linear import random_invertible_binary_matrix
+        from qiskit.transpiler.passes import HighLevelSynthesis, HLSConfig
+
+        # Create a circuit with a linear function
+        mat = random_invertible_binary_matrix(7, seed=37)
+        qc = QuantumCircuit(7)
+        qc.append(LinearFunction(mat), [0, 1, 2, 3, 4, 5, 6])
+
+        # Run different methods with different parameters,
+        # choosing the best result in terms of depth.
+        hls_config = HLSConfig(
+            linear_function=[
+                ("pmh", {}),
+                ("pmh", {"use_inverted": True}),
+                ("pmh", {"use_transposed": True}),
+                ("pmh", {"use_inverted": True, "use_transposed": True}),
+                ("pmh", {"section_size": 1}),
+                ("pmh", {"section_size": 3}),
+                ("kms", {}),
+                ("kms", {"use_inverted": True}),
+            ],
+            plugin_selection="all",
+            plugin_evaluation_fn=lambda circuit: circuit.depth(),
+        )
+
+        # synthesize
+        qct = HighLevelSynthesis(hls_config=hls_config)(qc)
+
+    In the example, we run multiple synthesis methods with different parameters,
+    choosing the best circuit in terms of depth. Note that optimizing
+    ``circuit.size()`` instead would pick a different circuit.
diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py
index 5ab78af8f581..0f074865f419 100644
--- a/test/python/transpiler/test_high_level_synthesis.py
+++ b/test/python/transpiler/test_high_level_synthesis.py
@@ -65,6 +65,7 @@
 )
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
+
 # In what follows, we create two simple operations OpA and OpB, that potentially mimic
 # higher-level objects written by a user.
 # For OpA we define two synthesis methods:
@@ -586,6 +587,78 @@ def test_invert_and_transpose(self):
             self.assertEqual(qct.size(), 6)
             self.assertEqual(qct.depth(), 6)
 
+    def test_plugin_selection_all(self):
+        """Test setting plugin_selection to all."""
+
+        linear_function = LinearFunction(self.construct_linear_circuit(7))
+        qc = QuantumCircuit(7)
+        qc.append(linear_function, [0, 1, 2, 3, 4, 5, 6])
+
+        with self.subTest("sequential"):
+            # In the default "run sequential" mode, we stop as soon as a plugin
+            # in the list returns a circuit.
+            # For this specific example the default options lead to a suboptimal circuit.
+            hls_config = HLSConfig(linear_function=[("pmh", {}), ("pmh", {"use_inverted": True})])
+            qct = HighLevelSynthesis(hls_config=hls_config)(qc)
+            self.assertEqual(LinearFunction(qct), LinearFunction(qc))
+            self.assertEqual(qct.size(), 12)
+            self.assertEqual(qct.depth(), 8)
+
+        with self.subTest("all"):
+            # In the non-default "run all" mode, we examine all plugins in the list.
+            # For this specific example we get the better result for the second plugin in the list.
+            hls_config = HLSConfig(
+                linear_function=[("pmh", {}), ("pmh", {"use_inverted": True})],
+                plugin_selection="all",
+            )
+            qct = HighLevelSynthesis(hls_config=hls_config)(qc)
+            self.assertEqual(LinearFunction(qct), LinearFunction(qc))
+            self.assertEqual(qct.size(), 6)
+            self.assertEqual(qct.depth(), 6)
+
+    def test_plugin_selection_all_with_metrix(self):
+        """Test setting plugin_selection to all and specifying different evaluation functions."""
+
+        # The seed is chosen so that we get different best circuits depending on whether we
+        # want to minimize size or depth.
+        mat = random_invertible_binary_matrix(7, seed=37)
+        qc = QuantumCircuit(7)
+        qc.append(LinearFunction(mat), [0, 1, 2, 3, 4, 5, 6])
+
+        with self.subTest("size_fn"):
+            # We want to minimize the "size" (aka the number of gates) in the circuit
+            hls_config = HLSConfig(
+                linear_function=[
+                    ("pmh", {}),
+                    ("pmh", {"use_inverted": True}),
+                    ("pmh", {"use_transposed": True}),
+                    ("pmh", {"use_inverted": True, "use_transposed": True}),
+                ],
+                plugin_selection="all",
+                plugin_evaluation_fn=lambda qc: qc.size(),
+            )
+            qct = HighLevelSynthesis(hls_config=hls_config)(qc)
+            self.assertEqual(LinearFunction(qct), LinearFunction(qc))
+            self.assertEqual(qct.size(), 20)
+            self.assertEqual(qct.depth(), 15)
+
+        with self.subTest("depth_fn"):
+            # We want to minimize the "depth" (aka the number of layers) in the circuit
+            hls_config = HLSConfig(
+                linear_function=[
+                    ("pmh", {}),
+                    ("pmh", {"use_inverted": True}),
+                    ("pmh", {"use_transposed": True}),
+                    ("pmh", {"use_inverted": True, "use_transposed": True}),
+                ],
+                plugin_selection="all",
+                plugin_evaluation_fn=lambda qc: qc.depth(),
+            )
+            qct = HighLevelSynthesis(hls_config=hls_config)(qc)
+            self.assertEqual(LinearFunction(qct), LinearFunction(qc))
+            self.assertEqual(qct.size(), 23)
+            self.assertEqual(qct.depth(), 12)
+
 
 class TestKMSSynthesisLinearFunctionPlugin(QiskitTestCase):
     """Tests for the KMSSynthesisLinearFunction plugin for synthesizing linear functions."""

From c60a1ed63eef38be2590bbdd9c93709ac7eb94c8 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Wed, 1 May 2024 13:59:43 +0100
Subject: [PATCH 047/179] Add representation of `expr.Var` to `DAGCircuit`
 (#12204)

* Add representation of `expr.Var` to `DAGCircuit`

This adds basic support for `Var`s to `DAGCircuit`, enabling the base
representation using the same wire structure used for clbits.  This is
known to be an inefficient representation of the dataflow for clbits,
which can be read multiple times without implying an order between those
operations (unlike qubits for which reads and writes are more naturally
linked).  We're using this simpler representation to make a better
initial MVP; optimising the data-flow representation would come as part
of a larger effort within the `DAGCircuit`.

This commit adds support in `DAGCircuit` for:

- representation of `Var` nodes
- appending all operations that might contain `Var` nodes to the DAG and
  updating the wire structure (including control-flow ops and stores)
- equality checking of DAGs with `Var`s (and so enables
  `QuantumCircuit.__eq__` as well)
- `DAGCircuit.copy_empty_like` with `Var`s
- the DAG/circuit converters

The other methods in `DAGCircuit` that might need to be aware of `Var`
nodes will be handled separately.

* Expand test coverage

* Fix copy/paste error
---
 qiskit/circuit/controlflow/control_flow.py    |  10 +
 qiskit/converters/circuit_to_dag.py           |   7 +
 qiskit/converters/dag_to_circuit.py           |   4 +
 qiskit/dagcircuit/dagcircuit.py               | 309 ++++++++++++++----
 test/python/converters/test_circuit_to_dag.py |  36 +-
 test/python/dagcircuit/test_dagcircuit.py     | 245 +++++++++++++-
 6 files changed, 538 insertions(+), 73 deletions(-)

diff --git a/qiskit/circuit/controlflow/control_flow.py b/qiskit/circuit/controlflow/control_flow.py
index 51b3709db6b5..2085f760ebcd 100644
--- a/qiskit/circuit/controlflow/control_flow.py
+++ b/qiskit/circuit/controlflow/control_flow.py
@@ -22,6 +22,7 @@
 
 if typing.TYPE_CHECKING:
     from qiskit.circuit import QuantumCircuit
+    from qiskit.circuit.classical import expr
 
 
 class ControlFlowOp(Instruction, ABC):
@@ -72,3 +73,12 @@ def map_block(block: QuantumCircuit) -> QuantumCircuit:
         Returns:
             New :class:`ControlFlowOp` with replaced blocks.
         """
+
+    def iter_captured_vars(self) -> typing.Iterable[expr.Var]:
+        """Get an iterator over the unique captured variables in all blocks of this construct."""
+        seen = set()
+        for block in self.blocks:
+            for var in block.iter_captured_vars():
+                if var not in seen:
+                    seen.add(var)
+                    yield var
diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py
index e2612b43d3e6..b2c1df2a037b 100644
--- a/qiskit/converters/circuit_to_dag.py
+++ b/qiskit/converters/circuit_to_dag.py
@@ -79,6 +79,13 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord
     dagcircuit.add_qubits(qubits)
     dagcircuit.add_clbits(clbits)
 
+    for var in circuit.iter_input_vars():
+        dagcircuit.add_input_var(var)
+    for var in circuit.iter_captured_vars():
+        dagcircuit.add_captured_var(var)
+    for var in circuit.iter_declared_vars():
+        dagcircuit.add_declared_var(var)
+
     for register in circuit.qregs:
         dagcircuit.add_qreg(register)
 
diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py
index 5a32f0bba1e1..ede026c247c9 100644
--- a/qiskit/converters/dag_to_circuit.py
+++ b/qiskit/converters/dag_to_circuit.py
@@ -62,7 +62,11 @@ def dag_to_circuit(dag, copy_operations=True):
         *dag.cregs.values(),
         name=name,
         global_phase=dag.global_phase,
+        inputs=dag.iter_input_vars(),
+        captures=dag.iter_captured_vars(),
     )
+    for var in dag.iter_declared_vars():
+        circuit.add_uninitialized_var(var)
     circuit.metadata = dag.metadata
     circuit.calibrations = dag.calibrations
 
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index 8c1332a8e604..6f00a3b3ea77 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -22,10 +22,12 @@
 """
 from __future__ import annotations
 
-from collections import OrderedDict, defaultdict, deque, namedtuple
-from collections.abc import Callable, Sequence, Generator, Iterable
 import copy
+import enum
+import itertools
 import math
+from collections import OrderedDict, defaultdict, deque, namedtuple
+from collections.abc import Callable, Sequence, Generator, Iterable
 from typing import Any
 
 import numpy as np
@@ -39,7 +41,9 @@
     SwitchCaseOp,
     _classical_resource_map,
     Operation,
+    Store,
 )
+from qiskit.circuit.classical import expr
 from qiskit.circuit.controlflow import condition_resources, node_resources, CONTROL_FLOW_OP_NAMES
 from qiskit.circuit.quantumregister import QuantumRegister, Qubit
 from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
@@ -78,13 +82,24 @@ def __init__(self):
         # Cache of dag op node sort keys
         self._key_cache = {}
 
-        # Set of wires (Register,idx) in the dag
+        # Set of wire data in the DAG.  A wire is an owned unit of data.  Qubits are the primary
+        # wire type (and the only data that has _true_ wire properties from a read/write
+        # perspective), but clbits and classical `Var`s are too.  Note: classical registers are
+        # _not_ wires because the individual bits are the more fundamental unit.  We treat `Var`s
+        # as the entire wire (as opposed to individual bits of them) for scalability reasons; if a
+        # parametric program wants to parametrize over 16-bit angles, we can't scale to 1000s of
+        # those by tracking all 16 bits individually.
+        #
+        # Classical variables shouldn't be "wires"; it should be possible to have multiple reads
+        # without implying ordering.  The initial addition of the classical variables uses the
+        # existing wire structure as an MVP; we expect to handle this better in a new version of the
+        # transpiler IR that also handles control flow more properly.
         self._wires = set()
 
-        # Map from wire (Register,idx) to input nodes of the graph
+        # Map from wire to input nodes of the graph
         self.input_map = OrderedDict()
 
-        # Map from wire (Register,idx) to output nodes of the graph
+        # Map from wire to output nodes of the graph
         self.output_map = OrderedDict()
 
         # Directed multigraph whose nodes are inputs, outputs, or operations.
@@ -92,7 +107,7 @@ def __init__(self):
         # additional data about the operation, including the argument order
         # and parameter values.
         # Input nodes have out-degree 1 and output nodes have in-degree 1.
-        # Edges carry wire labels (reg,idx) and each operation has
+        # Edges carry wire labels and each operation has
         # corresponding in- and out-edges with the same wire labels.
         self._multi_graph = rx.PyDAG()
 
@@ -110,6 +125,16 @@ def __init__(self):
         # its index within that register.
         self._qubit_indices: dict[Qubit, BitLocations] = {}
         self._clbit_indices: dict[Clbit, BitLocations] = {}
+        # Tracking for the classical variables used in the circuit.  This contains the information
+        # needed to insert new nodes.  This is keyed by the name rather than the `Var` instance
+        # itself so we can ensure we don't allow shadowing or redefinition of names.
+        self._vars_info: dict[str, _DAGVarInfo] = {}
+        # Convenience stateful tracking for the individual types of nodes to allow things like
+        # comparisons between circuits to take place without needing to disambiguate the
+        # graph-specific usage information.
+        self._vars_by_type: dict[_DAGVarType, set[expr.Var]] = {
+            type_: set() for type_ in _DAGVarType
+        }
 
         self._global_phase: float | ParameterExpression = 0.0
         self._calibrations: dict[str, dict[tuple, Schedule]] = defaultdict(dict)
@@ -122,7 +147,11 @@ def __init__(self):
     @property
     def wires(self):
         """Return a list of the wires in order."""
-        return self.qubits + self.clbits
+        return (
+            self.qubits
+            + self.clbits
+            + [var for vars in self._vars_by_type.values() for var in vars]
+        )
 
     @property
     def node_counter(self):
@@ -297,6 +326,57 @@ def add_creg(self, creg):
                 )
                 self._add_wire(creg[j])
 
+    def add_input_var(self, var: expr.Var):
+        """Add an input variable to the circuit.
+
+        Args:
+            var: the variable to add."""
+        if self._vars_by_type[_DAGVarType.CAPTURE]:
+            raise DAGCircuitError("cannot add inputs to a circuit with captures")
+        self._add_var(var, _DAGVarType.INPUT)
+
+    def add_captured_var(self, var: expr.Var):
+        """Add a captured variable to the circuit.
+
+        Args:
+            var: the variable to add."""
+        if self._vars_by_type[_DAGVarType.INPUT]:
+            raise DAGCircuitError("cannot add captures to a circuit with inputs")
+        self._add_var(var, _DAGVarType.CAPTURE)
+
+    def add_declared_var(self, var: expr.Var):
+        """Add a declared local variable to the circuit.
+
+        Args:
+            var: the variable to add."""
+        self._add_var(var, _DAGVarType.DECLARE)
+
+    def _add_var(self, var: expr.Var, type_: _DAGVarType):
+        """Inner function to add any variable to the DAG.  ``location`` should be a reference one of
+        the ``self._vars_*`` tracking dictionaries.
+        """
+        # The setup of the initial graph structure between an "in" and an "out" node is the same as
+        # the bit-related `_add_wire`, but this logically needs to do different bookkeeping around
+        # tracking the properties.
+        if not var.standalone:
+            raise DAGCircuitError(
+                "cannot add variables that wrap `Clbit` or `ClassicalRegister` instances"
+            )
+        if (previous := self._vars_info.get(var.name, None)) is not None:
+            if previous.var == var:
+                raise DAGCircuitError(f"'{var}' is already present in the circuit")
+            raise DAGCircuitError(
+                f"cannot add '{var}' as its name shadows the existing '{previous.var}'"
+            )
+        in_node = DAGInNode(wire=var)
+        out_node = DAGOutNode(wire=var)
+        in_node._node_id, out_node._node_id = self._multi_graph.add_nodes_from((in_node, out_node))
+        self._multi_graph.add_edge(in_node._node_id, out_node._node_id, var)
+        self.input_map[var] = in_node
+        self.output_map[var] = out_node
+        self._vars_by_type[type_].add(var)
+        self._vars_info[var.name] = _DAGVarInfo(var, type_, in_node, out_node)
+
     def _add_wire(self, wire):
         """Add a qubit or bit to the circuit.
 
@@ -543,14 +623,14 @@ def _check_condition(self, name, condition):
         if not set(resources.clbits).issubset(self.clbits):
             raise DAGCircuitError(f"invalid clbits in condition for {name}")
 
-    def _check_bits(self, args, amap):
-        """Check the values of a list of (qu)bit arguments.
+    def _check_wires(self, args: Iterable[Bit | expr.Var], amap: dict[Bit | expr.Var, Any]):
+        """Check the values of a list of wire arguments.
 
         For each element of args, check that amap contains it.
 
         Args:
-            args (list[Bit]): the elements to be checked
-            amap (dict): a dictionary keyed on Qubits/Clbits
+            args: the elements to be checked
+            amap: a dictionary keyed on Qubits/Clbits
 
         Raises:
             DAGCircuitError: if a qubit is not contained in amap
@@ -558,46 +638,7 @@ def _check_bits(self, args, amap):
         # Check for each wire
         for wire in args:
             if wire not in amap:
-                raise DAGCircuitError(f"(qu)bit {wire} not found in {amap}")
-
-    @staticmethod
-    def _bits_in_operation(operation):
-        """Return an iterable over the classical bits that are inherent to an instruction.  This
-        includes a `condition`, or the `target` of a :class:`.ControlFlowOp`.
-
-        Args:
-            instruction: the :class:`~.circuit.Instruction` instance for a node.
-
-        Returns:
-            Iterable[Clbit]: the :class:`.Clbit`\\ s involved.
-        """
-        # If updating this, also update the fast-path checker `DAGCirucit._operation_may_have_bits`.
-        if (condition := getattr(operation, "condition", None)) is not None:
-            yield from condition_resources(condition).clbits
-        if isinstance(operation, SwitchCaseOp):
-            target = operation.target
-            if isinstance(target, Clbit):
-                yield target
-            elif isinstance(target, ClassicalRegister):
-                yield from target
-            else:
-                yield from node_resources(target).clbits
-
-    @staticmethod
-    def _operation_may_have_bits(operation) -> bool:
-        """Return whether a given :class:`.Operation` may contain any :class:`.Clbit` instances
-        in itself (e.g. a control-flow operation).
-
-        Args:
-            operation (qiskit.circuit.Operation): the operation to check.
-        """
-        # This is separate to `_bits_in_operation` because most of the time there won't be any bits,
-        # so we want a fast path to be able to skip creating and testing a generator for emptiness.
-        #
-        # If updating this, also update `DAGCirucit._bits_in_operation`.
-        return getattr(operation, "condition", None) is not None or isinstance(
-            operation, SwitchCaseOp
-        )
+                raise DAGCircuitError(f"wire {wire} not found in {amap}")
 
     def _increment_op(self, op):
         if op.name in self._op_names:
@@ -618,7 +659,8 @@ def copy_empty_like(self):
             * name and other metadata
             * global phase
             * duration
-            * all the qubits and clbits, including the registers.
+            * all the qubits and clbits, including the registers
+            * all the classical variables.
 
         Returns:
             DAGCircuit: An empty copy of self.
@@ -639,6 +681,13 @@ def copy_empty_like(self):
         for creg in self.cregs.values():
             target_dag.add_creg(creg)
 
+        for var in self.iter_input_vars():
+            target_dag.add_input_var(var)
+        for var in self.iter_captured_vars():
+            target_dag.add_captured_var(var)
+        for var in self.iter_declared_vars():
+            target_dag.add_declared_var(var)
+
         return target_dag
 
     def apply_operation_back(
@@ -669,17 +718,17 @@ def apply_operation_back(
         """
         qargs = tuple(qargs)
         cargs = tuple(cargs)
+        additional = ()
 
-        if self._operation_may_have_bits(op):
+        if _may_have_additional_wires(op):
             # This is the slow path; most of the time, this won't happen.
-            all_cbits = set(self._bits_in_operation(op)).union(cargs)
-        else:
-            all_cbits = cargs
+            additional = set(_additional_wires(op)).difference(cargs)
 
         if check:
             self._check_condition(op.name, getattr(op, "condition", None))
-            self._check_bits(qargs, self.output_map)
-            self._check_bits(all_cbits, self.output_map)
+            self._check_wires(qargs, self.output_map)
+            self._check_wires(cargs, self.output_map)
+            self._check_wires(additional, self.output_map)
 
         node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self)
         node._node_id = self._multi_graph.add_node(node)
@@ -690,7 +739,7 @@ def apply_operation_back(
         # and adding new edges from the operation node to each output node
         self._multi_graph.insert_node_on_in_edges_multiple(
             node._node_id,
-            [self.output_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
+            [self.output_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
         )
         return node
 
@@ -721,17 +770,17 @@ def apply_operation_front(
         """
         qargs = tuple(qargs)
         cargs = tuple(cargs)
+        additional = ()
 
-        if self._operation_may_have_bits(op):
+        if _may_have_additional_wires(op):
             # This is the slow path; most of the time, this won't happen.
-            all_cbits = set(self._bits_in_operation(op)).union(cargs)
-        else:
-            all_cbits = cargs
+            additional = set(_additional_wires(op)).difference(cargs)
 
         if check:
             self._check_condition(op.name, getattr(op, "condition", None))
-            self._check_bits(qargs, self.input_map)
-            self._check_bits(all_cbits, self.input_map)
+            self._check_wires(qargs, self.output_map)
+            self._check_wires(cargs, self.output_map)
+            self._check_wires(additional, self.output_map)
 
         node = DAGOpNode(op=op, qargs=qargs, cargs=cargs, dag=self)
         node._node_id = self._multi_graph.add_node(node)
@@ -742,7 +791,7 @@ def apply_operation_front(
         # and adding new edges to the operation node from each input node
         self._multi_graph.insert_node_on_out_edges_multiple(
             node._node_id,
-            [self.input_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
+            [self.input_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
         )
         return node
 
@@ -1030,6 +1079,42 @@ def num_tensor_factors(self):
         """Compute how many components the circuit can decompose into."""
         return rx.number_weakly_connected_components(self._multi_graph)
 
+    @property
+    def num_vars(self):
+        """Total number of classical variables tracked by the circuit."""
+        return len(self._vars_info)
+
+    @property
+    def num_input_vars(self):
+        """Number of input classical variables tracked by the circuit."""
+        return len(self._vars_by_type[_DAGVarType.INPUT])
+
+    @property
+    def num_captured_vars(self):
+        """Number of captured classical variables tracked by the circuit."""
+        return len(self._vars_by_type[_DAGVarType.CAPTURE])
+
+    @property
+    def num_declared_vars(self):
+        """Number of declared local classical variables tracked by the circuit."""
+        return len(self._vars_by_type[_DAGVarType.DECLARE])
+
+    def iter_vars(self):
+        """Iterable over all the classical variables tracked by the circuit."""
+        return itertools.chain.from_iterable(self._vars_by_type.values())
+
+    def iter_input_vars(self):
+        """Iterable over the input classical variables tracked by the circuit."""
+        return iter(self._vars_by_type[_DAGVarType.INPUT])
+
+    def iter_captured_vars(self):
+        """Iterable over the captured classical variables tracked by the circuit."""
+        return iter(self._vars_by_type[_DAGVarType.CAPTURE])
+
+    def iter_declared_vars(self):
+        """Iterable over the declared local classical variables tracked by the circuit."""
+        return iter(self._vars_by_type[_DAGVarType.DECLARE])
+
     def __eq__(self, other):
         # Try to convert to float, but in case of unbound ParameterExpressions
         # a TypeError will be raise, fallback to normal equality in those
@@ -1047,6 +1132,11 @@ def __eq__(self, other):
         if self.calibrations != other.calibrations:
             return False
 
+        # We don't do any semantic equivalence between Var nodes, as things stand; DAGs can only be
+        # equal in our mind if they use the exact same UUID vars.
+        if self._vars_by_type != other._vars_by_type:
+            return False
+
         self_bit_indices = {bit: idx for idx, bit in enumerate(self.qubits + self.clbits)}
         other_bit_indices = {bit: idx for idx, bit in enumerate(other.qubits + other.clbits)}
 
@@ -1227,9 +1317,9 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit
             node_wire_order = list(node.qargs) + list(node.cargs)
             # If we're not propagating it, the number of wires in the input DAG should include the
             # condition as well.
-            if not propagate_condition and self._operation_may_have_bits(node.op):
+            if not propagate_condition and _may_have_additional_wires(node.op):
                 node_wire_order += [
-                    bit for bit in self._bits_in_operation(node.op) if bit not in node_cargs
+                    wire for wire in _additional_wires(node.op) if wire not in node_cargs
                 ]
             if len(wires) != len(node_wire_order):
                 raise DAGCircuitError(
@@ -1706,7 +1796,7 @@ def classical_predecessors(self, node):
         connected by a classical edge as DAGOpNodes and DAGInNodes."""
         return iter(
             self._multi_graph.find_predecessors_by_edge(
-                node._node_id, lambda edge_data: isinstance(edge_data, Clbit)
+                node._node_id, lambda edge_data: not isinstance(edge_data, Qubit)
             )
         )
 
@@ -1739,7 +1829,7 @@ def classical_successors(self, node):
         connected by a classical edge as DAGOpNodes and DAGInNodes."""
         return iter(
             self._multi_graph.find_successors_by_edge(
-                node._node_id, lambda edge_data: isinstance(edge_data, Clbit)
+                node._node_id, lambda edge_data: not isinstance(edge_data, Qubit)
             )
         )
 
@@ -2123,3 +2213,82 @@ def draw(self, scale=0.7, filename=None, style="color"):
         from qiskit.visualization.dag_visualization import dag_drawer
 
         return dag_drawer(dag=self, scale=scale, filename=filename, style=style)
+
+
+class _DAGVarType(enum.Enum):
+    INPUT = enum.auto()
+    CAPTURE = enum.auto()
+    DECLARE = enum.auto()
+
+
+class _DAGVarInfo:
+    __slots__ = ("var", "type", "in_node", "out_node")
+
+    def __init__(self, var: expr.Var, type_: _DAGVarType, in_node: DAGInNode, out_node: DAGOutNode):
+        self.var = var
+        self.type = type_
+        self.in_node = in_node
+        self.out_node = out_node
+
+
+def _may_have_additional_wires(operation) -> bool:
+    """Return whether a given :class:`.Operation` may contain references to additional wires
+    locations within itself.  If this is ``False``, it doesn't necessarily mean that the operation
+    _will_ access memory inherently, but a ``True`` return guarantees that it won't.
+
+    The memory might be classical bits or classical variables, such as a control-flow operation or a
+    store.
+
+    Args:
+        operation (qiskit.circuit.Operation): the operation to check.
+    """
+    # This is separate to `_additional_wires` because most of the time there won't be any extra
+    # wires beyond the explicit `qargs` and `cargs` so we want a fast path to be able to skip
+    # creating and testing a generator for emptiness.
+    #
+    # If updating this, you most likely also need to update `_additional_wires`.
+    return getattr(operation, "condition", None) is not None or isinstance(
+        operation, (ControlFlowOp, Store)
+    )
+
+
+def _additional_wires(operation) -> Iterable[Clbit | expr.Var]:
+    """Return an iterable over the additional tracked memory usage in this operation.  These
+    additional wires include (for example, non-exhaustive) bits referred to by a ``condition`` or
+    the classical variables involved in control-flow operations.
+
+    Args:
+        operation: the :class:`~.circuit.Operation` instance for a node.
+
+    Returns:
+        Iterable: the additional wires inherent to this operation.
+    """
+    # If updating this, you likely need to update `_may_have_additional_wires` too.
+    if (condition := getattr(operation, "condition", None)) is not None:
+        if isinstance(condition, expr.Expr):
+            yield from _wires_from_expr(condition)
+        else:
+            yield from condition_resources(condition).clbits
+    if isinstance(operation, ControlFlowOp):
+        yield from operation.iter_captured_vars()
+        if isinstance(operation, SwitchCaseOp):
+            target = operation.target
+            if isinstance(target, Clbit):
+                yield target
+            elif isinstance(target, ClassicalRegister):
+                yield from target
+            else:
+                yield from _wires_from_expr(target)
+    elif isinstance(operation, Store):
+        yield from _wires_from_expr(operation.lvalue)
+        yield from _wires_from_expr(operation.rvalue)
+
+
+def _wires_from_expr(node: expr.Expr) -> Iterable[Clbit | expr.Var]:
+    for var in expr.iter_vars(node):
+        if isinstance(var.var, Clbit):
+            yield var.var
+        elif isinstance(var.var, ClassicalRegister):
+            yield from var.var
+        else:
+            yield var
diff --git a/test/python/converters/test_circuit_to_dag.py b/test/python/converters/test_circuit_to_dag.py
index 4f2f52d03780..0bded9c0f4a2 100644
--- a/test/python/converters/test_circuit_to_dag.py
+++ b/test/python/converters/test_circuit_to_dag.py
@@ -15,9 +15,9 @@
 import unittest
 
 from qiskit.dagcircuit import DAGCircuit
-from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Clbit
+from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Clbit, SwitchCaseOp
 from qiskit.circuit.library import HGate, Measure
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from qiskit.converters import dag_to_circuit, circuit_to_dag
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
@@ -106,6 +106,38 @@ def test_wires_from_expr_nodes_target(self):
         for original, test in zip(outer, roundtripped):
             self.assertEqual(original.operation.target, test.operation.target)
 
+    def test_runtime_vars_in_roundtrip(self):
+        """`expr.Var` nodes should be fully roundtripped."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+        d = expr.Var.new("d", types.Uint(8))
+        qc = QuantumCircuit(inputs=[a, c])
+        qc.add_var(b, False)
+        qc.add_var(d, 255)
+        qc.store(a, expr.logic_or(a, b))
+        with qc.if_test(expr.logic_and(a, expr.equal(c, d))):
+            pass
+        with qc.while_loop(a):
+            qc.store(a, expr.logic_or(a, b))
+        with qc.switch(d) as case:
+            with case(0):
+                qc.store(c, d)
+            with case(case.DEFAULT):
+                qc.store(a, False)
+
+        roundtrip = dag_to_circuit(circuit_to_dag(qc))
+        self.assertEqual(qc, roundtrip)
+
+        self.assertIsInstance(qc.data[-1].operation, SwitchCaseOp)
+        # This is guaranteed to be topologically last, even after the DAG roundtrip.
+        self.assertIsInstance(roundtrip.data[-1].operation, SwitchCaseOp)
+        self.assertEqual(qc.data[-1].operation.blocks, roundtrip.data[-1].operation.blocks)
+
+        blocks = roundtrip.data[-1].operation.blocks
+        self.assertEqual(set(blocks[0].iter_captured_vars()), {c, d})
+        self.assertEqual(set(blocks[1].iter_captured_vars()), {a})
+
     def test_wire_order(self):
         """Test that the `qubit_order` and `clbit_order` parameters are respected."""
         permutation = [2, 3, 1, 4, 0, 5]  # Arbitrary.
diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py
index 3fcf5ff7a27e..62172d084ad3 100644
--- a/test/python/dagcircuit/test_dagcircuit.py
+++ b/test/python/dagcircuit/test_dagcircuit.py
@@ -38,8 +38,10 @@
     SwitchCaseOp,
     IfElseOp,
     WhileLoopOp,
+    CASE_DEFAULT,
+    Store,
 )
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import IGate, HGate, CXGate, CZGate, XGate, YGate, U1Gate, RXGate
 from qiskit.converters import circuit_to_dag
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
@@ -421,6 +423,22 @@ def test_copy_empty_like(self):
         self.assertEqual(self.dag.duration, result_dag.duration)
         self.assertEqual(self.dag.unit, result_dag.unit)
 
+    def test_copy_empty_like_vars(self):
+        """Variables should be part of the empty copy."""
+        dag = DAGCircuit()
+        dag.add_input_var(expr.Var.new("a", types.Bool()))
+        dag.add_input_var(expr.Var.new("b", types.Uint(8)))
+        dag.add_declared_var(expr.Var.new("c", types.Bool()))
+        dag.add_declared_var(expr.Var.new("d", types.Uint(8)))
+        self.assertEqual(dag, dag.copy_empty_like())
+
+        dag = DAGCircuit()
+        dag.add_captured_var(expr.Var.new("a", types.Bool()))
+        dag.add_captured_var(expr.Var.new("b", types.Uint(8)))
+        dag.add_declared_var(expr.Var.new("c", types.Bool()))
+        dag.add_declared_var(expr.Var.new("d", types.Uint(8)))
+        self.assertEqual(dag, dag.copy_empty_like())
+
     def test_remove_busy_clbit(self):
         """Classical bit removal of busy classical bits raises."""
         self.dag.apply_operation_back(Measure(), [self.qreg[0]], [self.individual_clbit])
@@ -1822,6 +1840,231 @@ def test_semantic_expr(self):
         qc2.switch(expr.bit_and(cr, 5), [(1, body)], [0], [])
         self.assertNotEqual(circuit_to_dag(qc1), circuit_to_dag(qc2))
 
+    def test_present_vars(self):
+        """The vars should be compared whether or not they're used."""
+        a_bool = expr.Var.new("a", types.Bool())
+        a_u8 = expr.Var.new("a", types.Uint(8))
+        a_u8_other = expr.Var.new("a", types.Uint(8))
+        b_bool = expr.Var.new("b", types.Bool())
+
+        left = DAGCircuit()
+        left.add_input_var(a_bool)
+        left.add_input_var(b_bool)
+        self.assertEqual(left.num_input_vars, 2)
+        self.assertEqual(left.num_captured_vars, 0)
+        self.assertEqual(left.num_declared_vars, 0)
+        self.assertEqual(left.num_vars, 2)
+
+        right = DAGCircuit()
+        right.add_input_var(a_bool)
+        right.add_input_var(b_bool)
+        self.assertEqual(right.num_input_vars, 2)
+        self.assertEqual(right.num_captured_vars, 0)
+        self.assertEqual(right.num_declared_vars, 0)
+        self.assertEqual(left.num_vars, 2)
+        self.assertEqual(left, right)
+
+        right = DAGCircuit()
+        right.add_input_var(a_u8)
+        right.add_input_var(b_bool)
+        self.assertEqual(right.num_input_vars, 2)
+        self.assertEqual(right.num_captured_vars, 0)
+        self.assertEqual(right.num_declared_vars, 0)
+        self.assertEqual(right.num_vars, 2)
+        self.assertNotEqual(left, right)
+
+        right = DAGCircuit()
+        self.assertEqual(right.num_input_vars, 0)
+        self.assertEqual(right.num_captured_vars, 0)
+        self.assertEqual(right.num_declared_vars, 0)
+        self.assertEqual(right.num_vars, 0)
+        self.assertNotEqual(left, right)
+
+        right = DAGCircuit()
+        right.add_captured_var(a_bool)
+        right.add_captured_var(b_bool)
+        self.assertEqual(right.num_input_vars, 0)
+        self.assertEqual(right.num_captured_vars, 2)
+        self.assertEqual(right.num_declared_vars, 0)
+        self.assertEqual(right.num_vars, 2)
+        self.assertNotEqual(left, right)
+
+        right = DAGCircuit()
+        right.add_declared_var(a_bool)
+        right.add_declared_var(b_bool)
+        self.assertEqual(right.num_input_vars, 0)
+        self.assertEqual(right.num_captured_vars, 0)
+        self.assertEqual(right.num_declared_vars, 2)
+        self.assertEqual(right.num_vars, 2)
+        self.assertNotEqual(left, right)
+
+        left = DAGCircuit()
+        left.add_captured_var(a_u8)
+
+        right = DAGCircuit()
+        right.add_captured_var(a_u8)
+        self.assertEqual(left, right)
+
+        right = DAGCircuit()
+        right.add_captured_var(a_u8_other)
+        self.assertNotEqual(left, right)
+
+    def test_wires_added_for_simple_classical_vars(self):
+        """Var uses should be represented in the wire structure."""
+        a = expr.Var.new("a", types.Bool())
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        self.assertEqual(list(dag.iter_vars()), [a])
+        self.assertEqual(list(dag.iter_input_vars()), [a])
+        self.assertEqual(list(dag.iter_captured_vars()), [])
+        self.assertEqual(list(dag.iter_declared_vars()), [])
+
+        expected_nodes = [dag.input_map[a], dag.output_map[a]]
+        self.assertEqual(list(dag.topological_nodes()), expected_nodes)
+        self.assertTrue(dag.is_successor(dag.input_map[a], dag.output_map[a]))
+
+        op_mid = dag.apply_operation_back(Store(a, expr.lift(True)), (), ())
+        self.assertTrue(dag.is_successor(dag.input_map[a], op_mid))
+        self.assertTrue(dag.is_successor(op_mid, dag.output_map[a]))
+        self.assertFalse(dag.is_successor(dag.input_map[a], dag.output_map[a]))
+
+        op_front = dag.apply_operation_front(Store(a, expr.logic_not(a)), (), ())
+        self.assertTrue(dag.is_successor(dag.input_map[a], op_front))
+        self.assertTrue(dag.is_successor(op_front, op_mid))
+        self.assertFalse(dag.is_successor(dag.input_map[a], op_mid))
+
+        op_back = dag.apply_operation_back(Store(a, expr.logic_not(a)), (), ())
+        self.assertTrue(dag.is_successor(op_mid, op_back))
+        self.assertTrue(dag.is_successor(op_back, dag.output_map[a]))
+        self.assertFalse(dag.is_successor(op_mid, dag.output_map[a]))
+
+    def test_wires_added_for_var_control_flow_condition(self):
+        """Vars used in if/else or while conditionals should be added to the wire structure."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        dag = DAGCircuit()
+        dag.add_declared_var(a)
+        dag.add_input_var(b)
+
+        op_store = dag.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        op_if = dag.apply_operation_back(IfElseOp(a, QuantumCircuit()), (), ())
+        op_while = dag.apply_operation_back(
+            WhileLoopOp(expr.logic_or(a, b), QuantumCircuit()), (), ()
+        )
+
+        expected_edges = {
+            (dag.input_map[a], op_store, a),
+            (op_store, op_if, a),
+            (op_if, op_while, a),
+            (op_while, dag.output_map[a], a),
+            (dag.input_map[b], op_while, b),
+            (op_while, dag.output_map[b], b),
+        }
+        self.assertEqual(set(dag.edges()), expected_edges)
+
+    def test_wires_added_for_var_control_flow_target(self):
+        """Vars used in switch targets should be added to the wire structure."""
+        a = expr.Var.new("a", types.Uint(8))
+        b = expr.Var.new("b", types.Uint(8))
+        dag = DAGCircuit()
+        dag.add_declared_var(a)
+        dag.add_input_var(b)
+
+        op_store = dag.apply_operation_back(Store(a, expr.lift(3, a.type)), (), ())
+        op_switch = dag.apply_operation_back(
+            SwitchCaseOp(expr.bit_xor(a, b), [(CASE_DEFAULT, QuantumCircuit())]), (), ()
+        )
+
+        expected_edges = {
+            (dag.input_map[a], op_store, a),
+            (op_store, op_switch, a),
+            (op_switch, dag.output_map[a], a),
+            (dag.input_map[b], op_switch, b),
+            (op_switch, dag.output_map[b], b),
+        }
+        self.assertEqual(set(dag.edges()), expected_edges)
+
+    def test_wires_added_for_control_flow_captures(self):
+        """Vars captured in control-flow blocks should be in the wire structure."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Bool())
+        d = expr.Var.new("d", types.Uint(8))
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        dag.add_declared_var(c)
+        dag.add_input_var(d)
+        op_store = dag.apply_operation_back(Store(c, expr.lift(False)), (), ())
+        op_if = dag.apply_operation_back(IfElseOp(a, QuantumCircuit(captures=[b])), (), ())
+        op_switch = dag.apply_operation_back(
+            SwitchCaseOp(
+                d,
+                [
+                    (0, QuantumCircuit(captures=[b])),
+                    (CASE_DEFAULT, QuantumCircuit(captures=[c])),
+                ],
+            ),
+            (),
+            (),
+        )
+
+        expected_edges = {
+            # a
+            (dag.input_map[a], op_if, a),
+            (op_if, dag.output_map[a], a),
+            # b
+            (dag.input_map[b], op_if, b),
+            (op_if, op_switch, b),
+            (op_switch, dag.output_map[b], b),
+            # c
+            (dag.input_map[c], op_store, c),
+            (op_store, op_switch, c),
+            (op_switch, dag.output_map[c], c),
+            # d
+            (dag.input_map[d], op_switch, d),
+            (op_switch, dag.output_map[d], d),
+        }
+        self.assertEqual(set(dag.edges()), expected_edges)
+
+    def test_forbid_mixing_captures_inputs(self):
+        """Test that a DAG can't have both captures and inputs."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        with self.assertRaisesRegex(
+            DAGCircuitError, "cannot add captures to a circuit with inputs"
+        ):
+            dag.add_captured_var(b)
+
+        dag = DAGCircuit()
+        dag.add_captured_var(a)
+        with self.assertRaisesRegex(
+            DAGCircuitError, "cannot add inputs to a circuit with captures"
+        ):
+            dag.add_input_var(b)
+
+    def test_forbid_adding_nonstandalone_var(self):
+        """Temporary "wrapping" vars aren't standalone and can't be tracked separately."""
+        dag = DAGCircuit()
+        with self.assertRaisesRegex(DAGCircuitError, "cannot add variables that wrap"):
+            dag.add_input_var(expr.lift(ClassicalRegister(4, "c")))
+        with self.assertRaisesRegex(DAGCircuitError, "cannot add variables that wrap"):
+            dag.add_declared_var(expr.lift(Clbit()))
+
+    def test_forbid_adding_conflicting_vars(self):
+        """Can't re-add a variable that exists, nor a shadowing variable in the same scope."""
+        a1 = expr.Var.new("a", types.Bool())
+        a2 = expr.Var.new("a", types.Bool())
+        dag = DAGCircuit()
+        dag.add_declared_var(a1)
+        with self.assertRaisesRegex(DAGCircuitError, "already present in the circuit"):
+            dag.add_declared_var(a1)
+        with self.assertRaisesRegex(DAGCircuitError, "cannot add .* as its name shadows"):
+            dag.add_declared_var(a2)
+
 
 class TestDagSubstitute(QiskitTestCase):
     """Test substituting a dag node with a sub-dag"""

From b40a34e9f24410855ffbcbacc857de10af57f08d Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Wed, 1 May 2024 09:10:09 -0400
Subject: [PATCH 048/179] fix UnitaryOverlap docstring tex (#12306)

---
 qiskit/circuit/library/overlap.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/qiskit/circuit/library/overlap.py b/qiskit/circuit/library/overlap.py
index ed86d8abb9a2..38f5fb9184e1 100644
--- a/qiskit/circuit/library/overlap.py
+++ b/qiskit/circuit/library/overlap.py
@@ -26,11 +26,11 @@ class UnitaryOverlap(QuantumCircuit):
     names `"p1"` (for circuit ``unitary1``) and `"p2"` (for circuit ``unitary_2``) in the output
     circuit.
 
-    This circuit is usually employed in computing the fidelity::
+    This circuit is usually employed in computing the fidelity:
 
-        .. math::
+    .. math::
 
-            \left|\langle 0| U_2^{\dag} U_1|0\rangle\right|^{2}
+        \left|\langle 0| U_2^{\dag} U_1|0\rangle\right|^{2}
 
     by computing the probability of being in the all-zeros bit-string, or equivalently,
     the expectation value of projector :math:`|0\rangle\langle 0|`.

From a41690d6075ac9438ff01b2346a35aa21dc3a568 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Wed, 1 May 2024 16:41:29 +0100
Subject: [PATCH 049/179] Reject vars in `QuantumCircuit.to_instruction` and
 `to_gate` (#12207)

* Reject vars in `QuantumCircuit.to_instruction` and `to_gate`

`QuantumCircuit.to_gate` may be able to support `input` variables that
are angle-typed at some point in the future, but that's very limited and
not immediately on the roadmap.

`QuantumCircuit.to_instruction` does have a natural way to support both
`input` vars (they become function arguments) and declared vars (since
they're completely internal to the function body), but those are
currently rejected because we don't have a way of specifying function
signatures / calling functions yet and we don't have a neat complete
story around unrolling variables into circuits yet (where there may be
naming clashes), especially through transpilation.  This opts to raise
the errors immediately, with alternatives where possible, rather than
letting things _kind of_ work.

* Remove now-unused loop
---
 qiskit/converters/circuit_to_gate.py          |  2 ++
 qiskit/converters/circuit_to_instruction.py   | 22 +++++++++++++
 .../python/converters/test_circuit_to_gate.py | 14 ++++++++
 .../converters/test_circuit_to_instruction.py | 33 +++++++++++++++++++
 4 files changed, 71 insertions(+)

diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py
index 283dd87dbd71..39eed1053eb1 100644
--- a/qiskit/converters/circuit_to_gate.py
+++ b/qiskit/converters/circuit_to_gate.py
@@ -58,6 +58,8 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label
 
     if circuit.clbits:
         raise QiskitError("Circuit with classical bits cannot be converted to gate.")
+    if circuit.num_vars:
+        raise QiskitError("circuits with realtime classical variables cannot be converted to gates")
 
     for instruction in circuit.data:
         if not _check_is_gate(instruction.operation):
diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py
index e4bba13b0334..2bdcbfef3583 100644
--- a/qiskit/converters/circuit_to_instruction.py
+++ b/qiskit/converters/circuit_to_instruction.py
@@ -61,6 +61,28 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None
     # pylint: disable=cyclic-import
     from qiskit.circuit.quantumcircuit import QuantumCircuit
 
+    if circuit.num_input_vars:
+        # This could be supported by moving the `input` variables to be parameters of the
+        # instruction, but we don't really have a good reprssentation of that yet, so safer to
+        # forbid it.
+        raise QiskitError("Circuits with 'input' variables cannot yet be converted to instructions")
+    if circuit.num_captured_vars:
+        raise QiskitError("Circuits that capture variables cannot be converted to instructions")
+    if circuit.num_declared_vars:
+        # This could very easily be supported in representations, since the variables are allocated
+        # and freed within the instruction itself.  The reason to initially forbid it is to avoid
+        # needing to support unrolling such instructions within the transpiler; we would potentially
+        # need to remap variables to unique names in the larger context, and we don't yet have a way
+        # to return that information from the transpiler.  We have to catch that in the transpiler
+        # as well since a user could manually make an instruction with such a definition, but
+        # forbidding it here means users get a more meaningful error at the point that the
+        # instruction actually gets created (since users often aren't aware that
+        # `QuantumCircuit.append(QuantumCircuit)` implicitly converts to an instruction).
+        raise QiskitError(
+            "Circuits with internal variables cannot yet be converted to instructions."
+            " You may be able to use `QuantumCircuit.compose` to inline this circuit into another."
+        )
+
     if parameter_map is None:
         parameter_dict = {p: p for p in circuit.parameters}
     else:
diff --git a/test/python/converters/test_circuit_to_gate.py b/test/python/converters/test_circuit_to_gate.py
index de3ad079e566..8e71a7f595a2 100644
--- a/test/python/converters/test_circuit_to_gate.py
+++ b/test/python/converters/test_circuit_to_gate.py
@@ -18,6 +18,7 @@
 
 from qiskit import QuantumRegister, QuantumCircuit
 from qiskit.circuit import Gate, Qubit
+from qiskit.circuit.classical import expr, types
 from qiskit.quantum_info import Operator
 from qiskit.exceptions import QiskitError
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
@@ -122,3 +123,16 @@ def test_zero_operands(self):
         compound = QuantumCircuit(1)
         compound.append(gate, [], [])
         np.testing.assert_allclose(-np.eye(2), Operator(compound), atol=1e-16)
+
+    def test_realtime_vars_rejected(self):
+        """Gates can't have realtime variables."""
+        qc = QuantumCircuit(1, inputs=[expr.Var.new("a", types.Bool())])
+        with self.assertRaisesRegex(QiskitError, "circuits with realtime classical variables"):
+            qc.to_gate()
+        qc = QuantumCircuit(1, captures=[expr.Var.new("a", types.Bool())])
+        with self.assertRaisesRegex(QiskitError, "circuits with realtime classical variables"):
+            qc.to_gate()
+        qc = QuantumCircuit(1)
+        qc.add_var("a", False)
+        with self.assertRaisesRegex(QiskitError, "circuits with realtime classical variables"):
+            qc.to_gate()
diff --git a/test/python/converters/test_circuit_to_instruction.py b/test/python/converters/test_circuit_to_instruction.py
index 56a227dbad92..d4b69e71aa10 100644
--- a/test/python/converters/test_circuit_to_instruction.py
+++ b/test/python/converters/test_circuit_to_instruction.py
@@ -21,6 +21,7 @@
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit.circuit import Qubit, Clbit, Instruction
 from qiskit.circuit import Parameter
+from qiskit.circuit.classical import expr, types
 from qiskit.quantum_info import Operator
 from qiskit.exceptions import QiskitError
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
@@ -218,6 +219,38 @@ def test_zero_operands(self):
         compound.append(instruction, [], [])
         np.testing.assert_allclose(-np.eye(2), Operator(compound), atol=1e-16)
 
+    def test_forbids_captured_vars(self):
+        """Instructions (here an analogue of functions) cannot close over outer scopes."""
+        qc = QuantumCircuit(captures=[expr.Var.new("a", types.Bool())])
+        with self.assertRaisesRegex(QiskitError, "Circuits that capture variables cannot"):
+            qc.to_instruction()
+
+    def test_forbids_input_vars(self):
+        """This test can be relaxed when we have proper support for the behaviour.
+
+        This actually has a natural meaning; the input variables could become typed parameters.
+        We don't have a formal structure for managing that yet, though, so it's forbidden until the
+        library is ready for that."""
+        qc = QuantumCircuit(inputs=[expr.Var.new("a", types.Bool())])
+        with self.assertRaisesRegex(QiskitError, "Circuits with 'input' variables cannot"):
+            qc.to_instruction()
+
+    def test_forbids_declared_vars(self):
+        """This test can be relaxed when we have proper support for the behaviour.
+
+        This has a very natural representation, which needs basically zero special handling, since
+        the variables are necessarily entirely internal to the subroutine.  The reason it is
+        starting off as forbidden is because we don't have a good way to support variable renaming
+        during unrolling in transpilation, and we want the error to indicate an alternative at the
+        point the conversion happens."""
+        qc = QuantumCircuit()
+        qc.add_var("a", False)
+        with self.assertRaisesRegex(
+            QiskitError,
+            "Circuits with internal variables.*You may be able to use `QuantumCircuit.compose`",
+        ):
+            qc.to_instruction()
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)

From 6b73b58550da278ba0e0dbe06f851b3e623d5595 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Wed, 1 May 2024 21:12:26 +0100
Subject: [PATCH 050/179] Support `Var` in circuit-substitution methods
 (#12215)

* Support `Var` in `QuantumCircuit.compose`

This converts the operation-rewriting step of `QuantumCircuit.compose`
into one that can recurse through control-flow operations rewriting
variables (to avoid conflicts). This initial commit adds two ways of
combining the variables:

1. variables in the `other` are disjoint from those in `self`, and are
   simply added to them.  This makes it easy to join unrelated circuits.
2. variables marked as "captures" in `other` can be inlined onto
   existing variables in `self`.  This makes it possible to build up a
   circuit with variables layer-by-layer.

In support of objective 2, I also taught `copy_empty_like` a way to
produce a new base layer with all the declared variables converted to
"captures" to make it easier to produce new base layers.

I deliberately did not include any _automatic_ variable renaming because
the usability of that seemed very hard to do well; while the circuit can
easily be created in a unique way, the user would then be hard-pressed
to actually retrieve the new `Var` nodes afterwards.  Asking the user to
manually break naming collisions guarantees that they'll be able to find
their variables again afterwards.

* Support `Var` in `DAGCircuit.compose`

This similarly adds support for a `vars_mode` argument to
`DAGCircuit.copy_empty_like` to make using this more convenient.  It
threads the same argument through some of the `DAGCircuit` methods that
build up layers of DAGs, since this is more common here.

Unlike `QuantumCircuit.compose`, `DAGCircuit.compose` does not (yet?)
offer the ability to remap variables during the composition, because the
use-cases for direct `DAGCircuit` composition are typically less about
building up many circuits from scratch and more about rebuilding a DAG
from itself.  Not offering the option makes it simpler to implement.

* Support `Var` in `DAGCircuit.replace_block_with_op`

This is straightforwards, since the wire structure is the same; there's
not actually any changes needed except for a minor comment explaining
that it works automatically.

* Support `Var` in `DAGCircuit.substitute_node_with_dag`

This is marginally trickier, and to avoid encoding performance problems
for ourselves in the public API, we want to avoid any requirement for
ourselves to recurse.  The expectation is that use-cases of this will be
building the replacement DAG themselves, where it's easy for them to
arrange for the `Var`s to be the correct ones immediately.

This also allows nice things like substitutions that contract wire use
out completely, over all wire types, not just qubits.

* Support `Var` in `DAGCircuit.substitute_node`

The easiest way to do this is actually to simplify the code a whole
bunch using the wire helper methods.  It would have been automatically
supported, had those methods already been in use.

* Increase test coverage
---
 crates/circuit/src/circuit_data.rs            |  28 +-
 qiskit/circuit/_classical_resource_map.py     |   9 +-
 qiskit/circuit/library/blueprintcircuit.py    |  28 +-
 qiskit/circuit/quantumcircuit.py              | 231 ++++++++++++++---
 qiskit/dagcircuit/dagcircuit.py               | 205 +++++++++++----
 .../python/circuit/test_circuit_operations.py |  63 +++++
 test/python/circuit/test_compose.py           | 163 +++++++++++-
 test/python/dagcircuit/test_compose.py        |  88 ++++++-
 test/python/dagcircuit/test_dagcircuit.py     | 241 ++++++++++++++++++
 9 files changed, 950 insertions(+), 106 deletions(-)

diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs
index 07bab2c17c9b..590fc07e8f8b 100644
--- a/crates/circuit/src/circuit_data.rs
+++ b/crates/circuit/src/circuit_data.rs
@@ -459,21 +459,14 @@ impl CircuitData {
         clbits: Option<&Bound>,
     ) -> PyResult<()> {
         let mut temp = CircuitData::new(py, qubits, clbits, None, 0)?;
-        if temp.qubits_native.len() < self.qubits_native.len() {
-            return Err(PyValueError::new_err(format!(
-                "Replacement 'qubits' of size {:?} must contain at least {:?} bits.",
-                temp.qubits_native.len(),
-                self.qubits_native.len(),
-            )));
-        }
-        if temp.clbits_native.len() < self.clbits_native.len() {
-            return Err(PyValueError::new_err(format!(
-                "Replacement 'clbits' of size {:?} must contain at least {:?} bits.",
-                temp.clbits_native.len(),
-                self.clbits_native.len(),
-            )));
-        }
         if qubits.is_some() {
+            if temp.qubits_native.len() < self.qubits_native.len() {
+                return Err(PyValueError::new_err(format!(
+                    "Replacement 'qubits' of size {:?} must contain at least {:?} bits.",
+                    temp.qubits_native.len(),
+                    self.qubits_native.len(),
+                )));
+            }
             std::mem::swap(&mut temp.qubits, &mut self.qubits);
             std::mem::swap(&mut temp.qubits_native, &mut self.qubits_native);
             std::mem::swap(
@@ -482,6 +475,13 @@ impl CircuitData {
             );
         }
         if clbits.is_some() {
+            if temp.clbits_native.len() < self.clbits_native.len() {
+                return Err(PyValueError::new_err(format!(
+                    "Replacement 'clbits' of size {:?} must contain at least {:?} bits.",
+                    temp.clbits_native.len(),
+                    self.clbits_native.len(),
+                )));
+            }
             std::mem::swap(&mut temp.clbits, &mut self.clbits);
             std::mem::swap(&mut temp.clbits_native, &mut self.clbits_native);
             std::mem::swap(
diff --git a/qiskit/circuit/_classical_resource_map.py b/qiskit/circuit/_classical_resource_map.py
index cfbdd077bda4..454826d6035d 100644
--- a/qiskit/circuit/_classical_resource_map.py
+++ b/qiskit/circuit/_classical_resource_map.py
@@ -37,17 +37,20 @@ class VariableMapper(expr.ExprVisitor[expr.Expr]):
     ``ValueError`` will be raised instead.  The given ``add_register`` callable may choose to raise
     its own exception."""
 
-    __slots__ = ("target_cregs", "register_map", "bit_map", "add_register")
+    __slots__ = ("target_cregs", "register_map", "bit_map", "var_map", "add_register")
 
     def __init__(
         self,
         target_cregs: typing.Iterable[ClassicalRegister],
         bit_map: typing.Mapping[Bit, Bit],
+        var_map: typing.Mapping[expr.Var, expr.Var] | None = None,
+        *,
         add_register: typing.Callable[[ClassicalRegister], None] | None = None,
     ):
         self.target_cregs = tuple(target_cregs)
         self.register_map = {}
         self.bit_map = bit_map
+        self.var_map = var_map or {}
         self.add_register = add_register
 
     def _map_register(self, theirs: ClassicalRegister) -> ClassicalRegister:
@@ -127,9 +130,7 @@ def visit_var(self, node, /):
             return expr.Var(self.bit_map[node.var], node.type)
         if isinstance(node.var, ClassicalRegister):
             return expr.Var(self._map_register(node.var), node.type)
-        # Defensive against the expansion of the variable system; we don't want to silently do the
-        # wrong thing (which would be `return node` without mapping, right now).
-        raise RuntimeError(f"unhandled variable in 'compose': {node}")  # pragma: no cover
+        return self.var_map.get(node, node)
 
     def visit_value(self, node, /):
         return expr.Value(node.value, node.type)
diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py
index 3d1f5c77f44f..2bbd5ca5650a 100644
--- a/qiskit/circuit/library/blueprintcircuit.py
+++ b/qiskit/circuit/library/blueprintcircuit.py
@@ -128,11 +128,31 @@ def _append(self, instruction, _qargs=None, _cargs=None):
         return super()._append(instruction, _qargs, _cargs)
 
     def compose(
-        self, other, qubits=None, clbits=None, front=False, inplace=False, wrap=False, *, copy=True
+        self,
+        other,
+        qubits=None,
+        clbits=None,
+        front=False,
+        inplace=False,
+        wrap=False,
+        *,
+        copy=True,
+        var_remap=None,
+        inline_captures=False,
     ):
         if not self._is_built:
             self._build()
-        return super().compose(other, qubits, clbits, front, inplace, wrap, copy=copy)
+        return super().compose(
+            other,
+            qubits,
+            clbits,
+            front,
+            inplace,
+            wrap,
+            copy=copy,
+            var_remap=var_remap,
+            inline_captures=False,
+        )
 
     def inverse(self, annotated: bool = False):
         if not self._is_built:
@@ -180,10 +200,10 @@ def num_connected_components(self, unitary_only=False):
             self._build()
         return super().num_connected_components(unitary_only=unitary_only)
 
-    def copy_empty_like(self, name=None):
+    def copy_empty_like(self, name=None, *, vars_mode="alike"):
         if not self._is_built:
             self._build()
-        cpy = super().copy_empty_like(name=name)
+        cpy = super().copy_empty_like(name=name, vars_mode=vars_mode)
         # The base `copy_empty_like` will typically trigger code that `BlueprintCircuit` treats as
         # an "invalidation", so we have to manually restore properties deleted by that that
         # `copy_empty_like` is supposed to propagate.
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index b19269a49166..ad966b685e71 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -883,11 +883,31 @@ def compose(
         wrap: bool = False,
         *,
         copy: bool = True,
+        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.
 
         ``other`` can be narrower or of equal width to ``self``.
 
+        When dealing with realtime variables (:class:`.expr.Var` instances), there are two principal
+        strategies for using :meth:`compose`:
+
+        1. The ``other`` circuit is treated as entirely additive, including its variables.  The
+           variables in ``other`` must be entirely distinct from those in ``self`` (use
+           ``var_remap`` to help with this), and all variables in ``other`` will be declared anew in
+           the output with matching input/capture/local scoping to how they are in ``other``.  This
+           is generally what you want if you're joining two unrelated circuits.
+
+        2. The ``other`` circuit was created as an exact extension to ``self`` to be inlined onto
+           it, including acting on the existing variables in their states at the end of ``self``.
+           In this case, ``other`` should be created with all these variables to be inlined declared
+           as "captures", and then you can use ``inline_captures=True`` in this method to link them.
+           This is generally what you want if you're building up a circuit by defining layers
+           on-the-fly, or rebuilding a circuit using layers taken from itself.  You might find the
+           ``vars_mode="captures"`` argument to :meth:`copy_empty_like` useful to create each
+           layer's base, in this case.
+
         Args:
             other (qiskit.circuit.Instruction or QuantumCircuit):
                 (sub)circuit or instruction to compose onto self.  If not a :obj:`.QuantumCircuit`,
@@ -905,6 +925,24 @@ def compose(
                 the base circuit, in order to avoid unnecessary copies; in this case, it is not
                 valid to use ``other`` afterwards, and some instructions may have been mutated in
                 place.
+            var_remap (Mapping): mapping to use to rewrite :class:`.expr.Var` nodes in ``other`` as
+                they are inlined into ``self``.  This can be used to avoid naming conflicts.
+
+                Both keys and values can be given as strings or direct :class:`.expr.Var` instances.
+                If a key is a string, it matches any :class:`~.expr.Var` with the same name.  If a
+                value is a string, whenever a new key matches a it, a new :class:`~.expr.Var` is
+                created with the correct type.  If a value is a :class:`~.expr.Var`, its
+                :class:`~.expr.Expr.type` must exactly match that of the variable it is replacing.
+            inline_captures (bool): if ``True``, then all "captured" :class:`~.expr.Var` nodes in
+                the ``other`` :class:`.QuantumCircuit` are assumed to refer to variables already
+                declared in ``self`` (as any input/capture/local type), and the uses in ``other``
+                will apply to the existing variables.  If you want to build up a layer for an
+                existing circuit to use with :meth:`compose`, you might find the
+                ``vars_mode="captures"`` argument to :meth:`copy_empty_like` useful.  Any remapping
+                in ``vars_remap`` occurs before evaluating this variable inlining.
+
+                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.
 
         Returns:
             QuantumCircuit: the composed circuit (returns None if inplace==True).
@@ -961,6 +999,31 @@ def compose(
         # error that the user might want to correct in an interactive session.
         dest = self if inplace else self.copy()
 
+        var_remap = {} if var_remap is None else var_remap
+
+        # This doesn't use `functools.cache` so we can access it during the variable remapping of
+        # instructions.  We cache all replacement lookups for a) speed and b) to ensure that
+        # the same variable _always_ maps to the same replacement even if it's used in different
+        # places in the recursion tree (such as being a captured variable).
+        def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var:
+            # This is closing over an argument to `compose`.
+            nonlocal var_remap
+
+            if out := cache.get(var):
+                return out
+            if (replacement := var_remap.get(var)) or (replacement := var_remap.get(var.name)):
+                if isinstance(replacement, str):
+                    replacement = expr.Var.new(replacement, var.type)
+                if replacement.type != var.type:
+                    raise CircuitError(
+                        f"mismatched types in replacement for '{var.name}':"
+                        f" '{var.type}' cannot become '{replacement.type}'"
+                    )
+            else:
+                replacement = var
+            cache[var] = replacement
+            return replacement
+
         # As a special case, allow composing some clbits onto no clbits - normally the destination
         # has to be strictly larger. This allows composing final measurements onto unitary circuits.
         if isinstance(other, QuantumCircuit):
@@ -1044,38 +1107,100 @@ def compose(
         dest.unit = "dt"
         dest.global_phase += other.global_phase
 
-        if not other.data:
-            # Nothing left to do. Plus, accessing 'data' here is necessary
-            # to trigger any lazy building since we now access '_data'
-            # directly.
-            return None if inplace else dest
+        # This is required to trigger data builds if the `other` is an unbuilt `BlueprintCircuit`,
+        # so we can the access the complete `CircuitData` object at `_data`.
+        _ = other.data
 
-        variable_mapper = _classical_resource_map.VariableMapper(
-            dest.cregs, edge_map, dest.add_register
-        )
+        def copy_with_remapping(
+            source, dest, bit_map, var_map, inline_captures, new_qubits=None, new_clbits=None
+        ):
+            # Copy the instructions from `source` into `dest`, remapping variables in instructions
+            # according to `var_map`.  If `new_qubits` or `new_clbits` are given, the qubits and
+            # clbits of the source instruction are remapped to those as well.
+            for var in source.iter_input_vars():
+                dest.add_input(replace_var(var, var_map))
+            if inline_captures:
+                for var in source.iter_captured_vars():
+                    replacement = replace_var(var, var_map)
+                    if not dest.has_var(replace_var(var, var_map)):
+                        if var is replacement:
+                            raise CircuitError(
+                                f"Variable '{var}' to be inlined is not in the base circuit."
+                                " If you wanted it to be automatically added, use"
+                                " `inline_captures=False`."
+                            )
+                        raise CircuitError(
+                            f"Replacement '{replacement}' for variable '{var}' is not in the"
+                            " base circuit.  Is the replacement correct?"
+                        )
+            else:
+                for var in source.iter_captured_vars():
+                    dest.add_capture(replace_var(var, var_map))
+            for var in source.iter_declared_vars():
+                dest.add_uninitialized_var(replace_var(var, var_map))
+
+            def recurse_block(block):
+                # Recurse the remapping into a control-flow block.  Note that this doesn't remap the
+                # clbits within; the story around nested classical-register-based control-flow
+                # doesn't really work in the current data model, and we hope to replace it with
+                # `Expr`-based control-flow everywhere.
+                new_block = block.copy_empty_like()
+                new_block._vars_input = {}
+                new_block._vars_capture = {}
+                new_block._vars_local = {}
+                # For the recursion, we never want to inline captured variables because we're not
+                # copying onto a base that has variables.
+                copy_with_remapping(block, new_block, bit_map, var_map, inline_captures=False)
+                return new_block
+
+            variable_mapper = _classical_resource_map.VariableMapper(
+                dest.cregs, bit_map, var_map, add_register=dest.add_register
+            )
 
-        def map_vars(op):
-            n_op = op.copy() if copy else op
-            if (condition := getattr(n_op, "condition", None)) is not None:
-                n_op.condition = variable_mapper.map_condition(condition)
-            if isinstance(n_op, SwitchCaseOp):
-                n_op = n_op.copy() if n_op is op else n_op
-                n_op.target = variable_mapper.map_target(n_op.target)
-            return n_op
+            def map_vars(op):
+                n_op = op
+                is_control_flow = isinstance(n_op, ControlFlowOp)
+                if (
+                    not is_control_flow
+                    and (condition := getattr(n_op, "condition", None)) is not None
+                ):
+                    n_op = n_op.copy() if n_op is op and copy else n_op
+                    n_op.condition = variable_mapper.map_condition(condition)
+                elif is_control_flow:
+                    n_op = n_op.replace_blocks(recurse_block(block) for block in n_op.blocks)
+                    if isinstance(n_op, (IfElseOp, WhileLoopOp)):
+                        n_op.condition = variable_mapper.map_condition(n_op.condition)
+                    elif isinstance(n_op, SwitchCaseOp):
+                        n_op.target = variable_mapper.map_target(n_op.target)
+                elif isinstance(n_op, Store):
+                    n_op = Store(
+                        variable_mapper.map_expr(n_op.lvalue), variable_mapper.map_expr(n_op.rvalue)
+                    )
+                return n_op.copy() if n_op is op and copy else n_op
 
-        mapped_instrs: CircuitData = other._data.copy()
-        mapped_instrs.replace_bits(qubits=mapped_qubits, clbits=mapped_clbits)
-        mapped_instrs.map_ops(map_vars)
+            instructions = source._data.copy()
+            instructions.replace_bits(qubits=new_qubits, clbits=new_clbits)
+            instructions.map_ops(map_vars)
+            dest._current_scope().extend(instructions)
 
         append_existing = None
         if front:
             append_existing = dest._data.copy()
             dest.clear()
-
-        circuit_scope = dest._current_scope()
-        circuit_scope.extend(mapped_instrs)
+        copy_with_remapping(
+            other,
+            dest,
+            bit_map=edge_map,
+            # The actual `Var: Var` map gets built up from the more freeform user input as we
+            # encounter the variables, since the user might be using string keys to refer to more
+            # than one variable in separated scopes of control-flow operations.
+            var_map={},
+            inline_captures=inline_captures,
+            new_qubits=mapped_qubits,
+            new_clbits=mapped_clbits,
+        )
         if append_existing:
-            circuit_scope.extend(append_existing)
+            dest._current_scope().extend(append_existing)
 
         return None if inplace else dest
 
@@ -2520,7 +2645,7 @@ def num_tensor_factors(self) -> int:
         """
         return self.num_unitary_factors()
 
-    def copy(self, name: str | None = None) -> "QuantumCircuit":
+    def copy(self, name: str | None = None) -> typing.Self:
         """Copy the circuit.
 
         Args:
@@ -2556,24 +2681,47 @@ def memo_copy(op):
         )
         return cpy
 
-    def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit":
+    def copy_empty_like(
+        self,
+        name: str | None = None,
+        *,
+        vars_mode: Literal["alike", "captures", "drop"] = "alike",
+    ) -> typing.Self:
         """Return a copy of self with the same structure but empty.
 
         That structure includes:
-            * name, calibrations and other metadata
-            * global phase
-            * all the qubits and clbits, including the registers
+
+        * name, calibrations and other metadata
+        * global phase
+        * all the qubits and clbits, including the registers
+        * the realtime variables defined in the circuit, handled according to the ``vars`` keyword
+          argument.
 
         .. warning::
 
             If the circuit contains any local variable declarations (those added by the
             ``declarations`` argument to the circuit constructor, or using :meth:`add_var`), they
-            will be **uninitialized** in the output circuit.  You will need to manually add store
+            may be **uninitialized** in the output circuit.  You will need to manually add store
             instructions for them (see :class:`.Store` and :meth:`.QuantumCircuit.store`) to
             initialize them.
 
         Args:
-            name (str): Name for the copied circuit. If None, then the name stays the same.
+            name: Name for the copied circuit. If None, then the name stays the same.
+            vars_mode: The mode to handle realtime variables in.
+
+                alike
+                    The variables in the output circuit will have the same declaration semantics as
+                    in the original circuit.  For example, ``input`` variables in the source will be
+                    ``input`` variables in the output circuit.
+
+                captures
+                    All variables will be converted to captured variables.  This is useful when you
+                    are building a new layer for an existing circuit that you will want to
+                    :meth:`compose` onto the base, since :meth:`compose` can inline captures onto
+                    the base circuit (but not other variables).
+
+                drop
+                    The output circuit will have no variables defined.
 
         Returns:
             QuantumCircuit: An empty copy of self.
@@ -2591,12 +2739,23 @@ def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit":
         cpy._qubit_indices = self._qubit_indices.copy()
         cpy._clbit_indices = self._clbit_indices.copy()
 
-        # Note that this causes the local variables to be uninitialised, because the stores are not
-        # copied.  This can leave the circuit in a potentially dangerous state for users if they
-        # don't re-add initialiser stores.
-        cpy._vars_local = self._vars_local.copy()
-        cpy._vars_input = self._vars_input.copy()
-        cpy._vars_capture = self._vars_capture.copy()
+        if vars_mode == "alike":
+            # Note that this causes the local variables to be uninitialised, because the stores are
+            # not copied.  This can leave the circuit in a potentially dangerous state for users if
+            # they don't re-add initialiser stores.
+            cpy._vars_local = self._vars_local.copy()
+            cpy._vars_input = self._vars_input.copy()
+            cpy._vars_capture = self._vars_capture.copy()
+        elif vars_mode == "captures":
+            cpy._vars_local = {}
+            cpy._vars_input = {}
+            cpy._vars_capture = {var.name: var for var in self.iter_vars()}
+        elif vars_mode == "drop":
+            cpy._vars_local = {}
+            cpy._vars_input = {}
+            cpy._vars_capture = {}
+        else:  # pragma: no cover
+            raise ValueError(f"unknown vars_mode: '{vars_mode}'")
 
         cpy._parameter_table = ParameterTable()
         for parameter in getattr(cpy.global_phase, "parameters", ()):
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index 6f00a3b3ea77..838e9cfe0f86 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -28,7 +28,7 @@
 import math
 from collections import OrderedDict, defaultdict, deque, namedtuple
 from collections.abc import Callable, Sequence, Generator, Iterable
-from typing import Any
+from typing import Any, Literal
 
 import numpy as np
 import rustworkx as rx
@@ -56,6 +56,8 @@
 from qiskit.pulse import Schedule
 
 BitLocations = namedtuple("BitLocations", ("index", "registers"))
+# The allowable arguments to :meth:`DAGCircuit.copy_empty_like`'s ``vars_mode``.
+_VarsMode = Literal["alike", "captures", "drop"]
 
 
 class DAGCircuit:
@@ -652,7 +654,7 @@ def _decrement_op(self, op):
         else:
             self._op_names[op.name] -= 1
 
-    def copy_empty_like(self):
+    def copy_empty_like(self, *, vars_mode: _VarsMode = "alike"):
         """Return a copy of self with the same structure but empty.
 
         That structure includes:
@@ -660,7 +662,24 @@ def copy_empty_like(self):
             * global phase
             * duration
             * all the qubits and clbits, including the registers
-            * all the classical variables.
+            * all the classical variables, with a mode defined by ``vars_mode``.
+
+        Args:
+            vars_mode: The mode to handle realtime variables in.
+
+                alike
+                    The variables in the output DAG will have the same declaration semantics as
+                    in the original circuit.  For example, ``input`` variables in the source will be
+                    ``input`` variables in the output DAG.
+
+                captures
+                    All variables will be converted to captured variables.  This is useful when you
+                    are building a new layer for an existing DAG that you will want to
+                    :meth:`compose` onto the base, since :meth:`compose` can inline captures onto
+                    the base circuit (but not other variables).
+
+                drop
+                    The output DAG will have no variables defined.
 
         Returns:
             DAGCircuit: An empty copy of self.
@@ -681,12 +700,20 @@ def copy_empty_like(self):
         for creg in self.cregs.values():
             target_dag.add_creg(creg)
 
-        for var in self.iter_input_vars():
-            target_dag.add_input_var(var)
-        for var in self.iter_captured_vars():
-            target_dag.add_captured_var(var)
-        for var in self.iter_declared_vars():
-            target_dag.add_declared_var(var)
+        if vars_mode == "alike":
+            for var in self.iter_input_vars():
+                target_dag.add_input_var(var)
+            for var in self.iter_captured_vars():
+                target_dag.add_captured_var(var)
+            for var in self.iter_declared_vars():
+                target_dag.add_declared_var(var)
+        elif vars_mode == "captures":
+            for var in self.iter_vars():
+                target_dag.add_captured_var(var)
+        elif vars_mode == "drop":
+            pass
+        else:  # pragma: no cover
+            raise ValueError(f"unknown vars_mode: '{vars_mode}'")
 
         return target_dag
 
@@ -795,7 +822,9 @@ def apply_operation_front(
         )
         return node
 
-    def compose(self, other, qubits=None, clbits=None, front=False, inplace=True):
+    def compose(
+        self, other, qubits=None, clbits=None, front=False, inplace=True, *, inline_captures=False
+    ):
         """Compose the ``other`` circuit onto the output of this circuit.
 
         A subset of input wires of ``other`` are mapped
@@ -809,6 +838,18 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=True):
             clbits (list[Clbit|int]): clbits of self to compose onto.
             front (bool): If True, front composition will be performed (not implemented yet)
             inplace (bool): If True, modify the object. Otherwise return composed circuit.
+            inline_captures (bool): If ``True``, variables marked as "captures" in the ``other`` DAG
+                will inlined onto existing uses of those same variables in ``self``.  If ``False``,
+                all variables in ``other`` are required to be distinct from ``self``, and they will
+                be added to ``self``.
+
+        ..
+            Note: unlike `QuantumCircuit.compose`, there's no `var_remap` argument here.  That's
+            because the `DAGCircuit` inner-block structure isn't set up well to allow the recursion,
+            and `DAGCircuit.compose` is generally only used to rebuild a DAG from layers within
+            itself than to join unrelated circuits.  While there's no strong motivating use-case
+            (unlike the `QuantumCircuit` equivalent), it's safer and more performant to not provide
+            the option.
 
         Returns:
             DAGCircuit: the composed dag (returns None if inplace==True).
@@ -871,27 +912,52 @@ def compose(self, other, qubits=None, clbits=None, front=False, inplace=True):
         for gate, cals in other.calibrations.items():
             dag._calibrations[gate].update(cals)
 
+        # This is all the handling we need for realtime variables, if there's no remapping. They:
+        #
+        # * get added to the DAG and then operations involving them get appended on normally.
+        # * get inlined onto an existing variable, then operations get appended normally.
+        # * there's a clash or a failed inlining, and we just raise an error.
+        #
+        # Notably if there's no remapping, there's no need to recurse into control-flow or to do any
+        # Var rewriting during the Expr visits.
+        for var in other.iter_input_vars():
+            dag.add_input_var(var)
+        if inline_captures:
+            for var in other.iter_captured_vars():
+                if not dag.has_var(var):
+                    raise DAGCircuitError(
+                        f"Variable '{var}' to be inlined is not in the base DAG."
+                        " If you wanted it to be automatically added, use `inline_captures=False`."
+                    )
+        else:
+            for var in other.iter_captured_vars():
+                dag.add_captured_var(var)
+        for var in other.iter_declared_vars():
+            dag.add_declared_var(var)
+
         # Ensure that the error raised here is a `DAGCircuitError` for backwards compatibility.
         def _reject_new_register(reg):
             raise DAGCircuitError(f"No register with '{reg.bits}' to map this expression onto.")
 
         variable_mapper = _classical_resource_map.VariableMapper(
-            dag.cregs.values(), edge_map, _reject_new_register
+            dag.cregs.values(), edge_map, add_register=_reject_new_register
         )
         for nd in other.topological_nodes():
             if isinstance(nd, DAGInNode):
-                # if in edge_map, get new name, else use existing name
-                m_wire = edge_map.get(nd.wire, nd.wire)
-                # the mapped wire should already exist
-                if m_wire not in dag.output_map:
-                    raise DAGCircuitError(
-                        "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index)
-                    )
-                if nd.wire not in other._wires:
-                    raise DAGCircuitError(
-                        "inconsistent wire type for %s[%d] in other"
-                        % (nd.register.name, nd.wire.index)
-                    )
+                if isinstance(nd.wire, Bit):
+                    # if in edge_map, get new name, else use existing name
+                    m_wire = edge_map.get(nd.wire, nd.wire)
+                    # the mapped wire should already exist
+                    if m_wire not in dag.output_map:
+                        raise DAGCircuitError(
+                            "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index)
+                        )
+                    if nd.wire not in other._wires:
+                        raise DAGCircuitError(
+                            "inconsistent wire type for %s[%d] in other"
+                            % (nd.register.name, nd.wire.index)
+                        )
+                # If it's a Var wire, we already checked that it exists in the destination.
             elif isinstance(nd, DAGOutNode):
                 # ignore output nodes
                 pass
@@ -1115,6 +1181,16 @@ def iter_declared_vars(self):
         """Iterable over the declared local classical variables tracked by the circuit."""
         return iter(self._vars_by_type[_DAGVarType.DECLARE])
 
+    def has_var(self, var: str | expr.Var) -> bool:
+        """Is this realtime variable in the DAG?
+
+        Args:
+            var: the variable or name to check.
+        """
+        if isinstance(var, str):
+            return var in self._vars_info
+        return (info := self._vars_info.get(var.name, False)) and info.var is var
+
     def __eq__(self, other):
         # Try to convert to float, but in case of unbound ParameterExpressions
         # a TypeError will be raise, fallback to normal equality in those
@@ -1220,7 +1296,8 @@ def replace_block_with_op(
                 multiple gates in the combined single op node.  If a :class:`.Bit` is not in the
                 dictionary, it will not be added to the args; this can be useful when dealing with
                 control-flow operations that have inherent bits in their ``condition`` or ``target``
-                fields.
+                fields.  :class:`.expr.Var` wires similarly do not need to be in this map, since
+                they will never be in ``qargs`` or ``cargs``.
             cycle_check (bool): When set to True this method will check that
                 replacing the provided ``node_block`` with a single node
                 would introduce a cycle (which would invalidate the
@@ -1287,12 +1364,22 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit
 
         Args:
             node (DAGOpNode): node to substitute
-            input_dag (DAGCircuit): circuit that will substitute the node
+            input_dag (DAGCircuit): circuit that will substitute the node.
             wires (list[Bit] | Dict[Bit, Bit]): gives an order for (qu)bits
                 in the input circuit. If a list, then the bits refer to those in the ``input_dag``,
                 and the order gets matched to the node wires by qargs first, then cargs, then
                 conditions.  If a dictionary, then a mapping of bits in the ``input_dag`` to those
                 that the ``node`` acts on.
+
+                Standalone :class:`~.expr.Var` nodes cannot currently be remapped as part of the
+                substitution; the ``input_dag`` should be defined over the correct set of variables
+                already.
+
+                ..
+                    The rule about not remapping `Var`s is to avoid performance pitfalls and reduce
+                    complexity; the creator of the input DAG should easily be able to arrange for
+                    the correct `Var`s to be used, and doing so avoids us needing to recurse through
+                    control-flow operations to do deep remappings.
             propagate_condition (bool): If ``True`` (default), then any ``condition`` attribute on
                 the operation within ``node`` is propagated to each node in the ``input_dag``.  If
                 ``False``, then the ``input_dag`` is assumed to faithfully implement suitable
@@ -1331,12 +1418,27 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit
         for input_dag_wire, our_wire in wire_map.items():
             if our_wire not in self.input_map:
                 raise DAGCircuitError(f"bit mapping invalid: {our_wire} is not in this DAG")
+            if isinstance(our_wire, expr.Var) or isinstance(input_dag_wire, expr.Var):
+                raise DAGCircuitError("`Var` nodes cannot be remapped during substitution")
             # Support mapping indiscriminately between Qubit and AncillaQubit, etc.
             check_type = Qubit if isinstance(our_wire, Qubit) else Clbit
             if not isinstance(input_dag_wire, check_type):
                 raise DAGCircuitError(
                     f"bit mapping invalid: {input_dag_wire} and {our_wire} are different bit types"
                 )
+        if _may_have_additional_wires(node.op):
+            node_vars = {var for var in _additional_wires(node.op) if isinstance(var, expr.Var)}
+        else:
+            node_vars = set()
+        dag_vars = set(input_dag.iter_vars())
+        if dag_vars - node_vars:
+            raise DAGCircuitError(
+                "Cannot replace a node with a DAG with more variables."
+                f" Variables in node: {node_vars}."
+                f" Variables in DAG: {dag_vars}."
+            )
+        for var in dag_vars:
+            wire_map[var] = var
 
         reverse_wire_map = {b: a for a, b in wire_map.items()}
         # It doesn't make sense to try and propagate a condition from a control-flow op; a
@@ -1415,14 +1517,22 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit
                     node._node_id, lambda edge, wire=self_wire: edge == wire
                 )[0]
                 self._multi_graph.add_edge(pred._node_id, succ._node_id, self_wire)
+        for contracted_var in node_vars - dag_vars:
+            pred = self._multi_graph.find_predecessors_by_edge(
+                node._node_id, lambda edge, wire=contracted_var: edge == wire
+            )[0]
+            succ = self._multi_graph.find_successors_by_edge(
+                node._node_id, lambda edge, wire=contracted_var: edge == wire
+            )[0]
+            self._multi_graph.add_edge(pred._node_id, succ._node_id, contracted_var)
 
         # Exlude any nodes from in_dag that are not a DAGOpNode or are on
-        # bits outside the set specified by the wires kwarg
+        # wires outside the set specified by the wires kwarg
         def filter_fn(node):
             if not isinstance(node, DAGOpNode):
                 return False
-            for qarg in node.qargs:
-                if qarg not in wire_map:
+            for _, _, wire in in_dag.edges(node):
+                if wire not in wire_map:
                     return False
             return True
 
@@ -1459,7 +1569,7 @@ def edge_weight_map(wire):
         self._decrement_op(node.op)
 
         variable_mapper = _classical_resource_map.VariableMapper(
-            self.cregs.values(), wire_map, self.add_creg
+            self.cregs.values(), wire_map, add_register=self.add_creg
         )
         # Iterate over nodes of input_circuit and update wires in node objects migrated
         # from in_dag
@@ -1531,21 +1641,12 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_
         # This might include wires that are inherent to the node, like in its `condition` or
         # `target` fields, so might be wider than `node.op.num_{qu,cl}bits`.
         current_wires = {wire for _, _, wire in self.edges(node)}
-        new_wires = set(node.qargs) | set(node.cargs)
-        if (new_condition := getattr(op, "condition", None)) is not None:
-            new_wires.update(condition_resources(new_condition).clbits)
-        elif isinstance(op, SwitchCaseOp):
-            if isinstance(op.target, Clbit):
-                new_wires.add(op.target)
-            elif isinstance(op.target, ClassicalRegister):
-                new_wires.update(op.target)
-            else:
-                new_wires.update(node_resources(op.target).clbits)
+        new_wires = set(node.qargs) | set(node.cargs) | set(_additional_wires(op))
 
         if propagate_condition and not (
             isinstance(node.op, ControlFlowOp) or isinstance(op, ControlFlowOp)
         ):
-            if new_condition is not None:
+            if getattr(op, "condition", None) is not None:
                 raise DAGCircuitError(
                     "Cannot propagate a condition to an operation that already has one."
                 )
@@ -1581,13 +1682,17 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_
             self._decrement_op(node.op)
         return new_node
 
-    def separable_circuits(self, remove_idle_qubits: bool = False) -> list["DAGCircuit"]:
+    def separable_circuits(
+        self, remove_idle_qubits: bool = False, *, vars_mode: _VarsMode = "alike"
+    ) -> list["DAGCircuit"]:
         """Decompose the circuit into sets of qubits with no gates connecting them.
 
         Args:
             remove_idle_qubits (bool): Flag denoting whether to remove idle qubits from
                 the separated circuits. If ``False``, each output circuit will contain the
                 same number of qubits as ``self``.
+            vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
+                DAGs.  See :meth:`copy_empty_like` for details on the modes.
 
         Returns:
             List[DAGCircuit]: The circuits resulting from separating ``self`` into sets
@@ -1612,7 +1717,7 @@ def _key(x):
         # Create new DAGCircuit objects from each of the rustworkx subgraph objects
         decomposed_dags = []
         for subgraph in disconnected_subgraphs:
-            new_dag = self.copy_empty_like()
+            new_dag = self.copy_empty_like(vars_mode=vars_mode)
             new_dag.global_phase = 0
             subgraph_is_classical = True
             for node in rx.lexicographical_topological_sort(subgraph, key=_key):
@@ -1894,7 +1999,7 @@ def front_layer(self):
 
         return op_nodes
 
-    def layers(self):
+    def layers(self, *, vars_mode: _VarsMode = "captures"):
         """Yield a shallow view on a layer of this DAGCircuit for all d layers of this circuit.
 
         A layer is a circuit whose gates act on disjoint qubits, i.e.,
@@ -1911,6 +2016,10 @@ def layers(self):
         TODO: Gates that use the same cbits will end up in different
         layers as this is currently implemented. This may not be
         the desired behavior.
+
+        Args:
+            vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
+                DAGs.  See :meth:`copy_empty_like` for details on the modes.
         """
         graph_layers = self.multigraph_layers()
         try:
@@ -1935,7 +2044,7 @@ def layers(self):
                 return
 
             # Construct a shallow copy of self
-            new_layer = self.copy_empty_like()
+            new_layer = self.copy_empty_like(vars_mode=vars_mode)
 
             for node in op_nodes:
                 # this creates new DAGOpNodes in the new_layer
@@ -1950,14 +2059,18 @@ def layers(self):
 
             yield {"graph": new_layer, "partition": support_list}
 
-    def serial_layers(self):
+    def serial_layers(self, *, vars_mode: _VarsMode = "captures"):
         """Yield a layer for all gates of this circuit.
 
         A serial layer is a circuit with one gate. The layers have the
         same structure as in layers().
+
+        Args:
+            vars_mode: how any realtime :class:`~.expr.Var` nodes should be handled in the output
+                DAGs.  See :meth:`copy_empty_like` for details on the modes.
         """
         for next_node in self.topological_op_nodes():
-            new_layer = self.copy_empty_like()
+            new_layer = self.copy_empty_like(vars_mode=vars_mode)
 
             # Save the support of the operation we add to the layer
             support_list = []
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index 483224196798..9a934d70c710 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -485,6 +485,69 @@ def test_copy_empty_variables(self):
         self.assertEqual({b, d}, set(copied.iter_captured_vars()))
         self.assertEqual({b}, set(qc.iter_captured_vars()))
 
+    def test_copy_empty_variables_alike(self):
+        """Test that an empty copy of circuits including variables copies them across, but does not
+        initialise them.  This is the same as the default, just spelled explicitly."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Bool())
+        d = expr.Var.new("d", types.Uint(8))
+
+        qc = QuantumCircuit(inputs=[a], declarations=[(c, expr.lift(False))])
+        copied = qc.copy_empty_like(vars_mode="alike")
+        self.assertEqual({a}, set(copied.iter_input_vars()))
+        self.assertEqual({c}, set(copied.iter_declared_vars()))
+        self.assertEqual([], list(copied.data))
+
+        # Check that the original circuit is not mutated.
+        copied.add_input(b)
+        copied.add_var(d, 0xFF)
+        self.assertEqual({a, b}, set(copied.iter_input_vars()))
+        self.assertEqual({c, d}, set(copied.iter_declared_vars()))
+        self.assertEqual({a}, set(qc.iter_input_vars()))
+        self.assertEqual({c}, set(qc.iter_declared_vars()))
+
+        qc = QuantumCircuit(captures=[b], declarations=[(a, expr.lift(False)), (c, a)])
+        copied = qc.copy_empty_like(vars_mode="alike")
+        self.assertEqual({b}, set(copied.iter_captured_vars()))
+        self.assertEqual({a, c}, set(copied.iter_declared_vars()))
+        self.assertEqual([], list(copied.data))
+
+        # Check that the original circuit is not mutated.
+        copied.add_capture(d)
+        self.assertEqual({b, d}, set(copied.iter_captured_vars()))
+        self.assertEqual({b}, set(qc.iter_captured_vars()))
+
+    def test_copy_empty_variables_to_captures(self):
+        """``vars_mode="captures"`` should convert all variables to captures."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Bool())
+        d = expr.Var.new("d", types.Uint(8))
+
+        qc = QuantumCircuit(inputs=[a, b], declarations=[(c, expr.lift(False))])
+        copied = qc.copy_empty_like(vars_mode="captures")
+        self.assertEqual({a, b, c}, set(copied.iter_captured_vars()))
+        self.assertEqual({a, b, c}, set(copied.iter_vars()))
+        self.assertEqual([], list(copied.data))
+
+        qc = QuantumCircuit(captures=[c, d])
+        copied = qc.copy_empty_like(vars_mode="captures")
+        self.assertEqual({c, d}, set(copied.iter_captured_vars()))
+        self.assertEqual({c, d}, set(copied.iter_vars()))
+        self.assertEqual([], list(copied.data))
+
+    def test_copy_empty_variables_drop(self):
+        """``vars_mode="drop"`` should not have variables in the output."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Bool())
+
+        qc = QuantumCircuit(inputs=[a, b], declarations=[(c, expr.lift(False))])
+        copied = qc.copy_empty_like(vars_mode="drop")
+        self.assertEqual(set(), set(copied.iter_vars()))
+        self.assertEqual([], list(copied.data))
+
     def test_copy_empty_like_parametric_phase(self):
         """Test that the parameter table of an empty circuit remains valid after copying a circuit
         with a parametric global phase."""
diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py
index 03301899a6ae..0e481c12b33b 100644
--- a/test/python/circuit/test_compose.py
+++ b/test/python/circuit/test_compose.py
@@ -34,7 +34,7 @@
     CircuitError,
 )
 from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -901,6 +901,118 @@ def test_expr_target_is_mapped(self):
 
         self.assertEqual(dest, expected)
 
+    def test_join_unrelated_vars(self):
+        """Composing disjoint sets of vars should produce an additive output."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        base = QuantumCircuit(inputs=[a])
+        other = QuantumCircuit(inputs=[b])
+        out = base.compose(other)
+        self.assertEqual({a, b}, set(out.iter_vars()))
+        self.assertEqual({a, b}, set(out.iter_input_vars()))
+        # Assert that base was unaltered.
+        self.assertEqual({a}, set(base.iter_vars()))
+
+        base = QuantumCircuit(captures=[a])
+        other = QuantumCircuit(captures=[b])
+        out = base.compose(other)
+        self.assertEqual({a, b}, set(out.iter_vars()))
+        self.assertEqual({a, b}, set(out.iter_captured_vars()))
+        self.assertEqual({a}, set(base.iter_vars()))
+
+        base = QuantumCircuit(inputs=[a])
+        other = QuantumCircuit(declarations=[(b, 255)])
+        out = base.compose(other)
+        self.assertEqual({a, b}, set(out.iter_vars()))
+        self.assertEqual({a}, set(out.iter_input_vars()))
+        self.assertEqual({b}, set(out.iter_declared_vars()))
+
+    def test_var_remap_to_avoid_collisions(self):
+        """We can use `var_remap` to avoid a variable collision."""
+        a1 = expr.Var.new("a", types.Bool())
+        a2 = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        base = QuantumCircuit(inputs=[a1])
+        other = QuantumCircuit(inputs=[a2])
+
+        out = base.compose(other, var_remap={a2: b})
+        self.assertEqual([a1, b], list(out.iter_input_vars()))
+        self.assertEqual([a1, b], list(out.iter_vars()))
+
+        out = base.compose(other, var_remap={"a": b})
+        self.assertEqual([a1, b], list(out.iter_input_vars()))
+        self.assertEqual([a1, b], list(out.iter_vars()))
+
+        out = base.compose(other, var_remap={"a": "c"})
+        self.assertTrue(out.has_var("c"))
+        c = out.get_var("c")
+        self.assertEqual(c.name, "c")
+        self.assertEqual([a1, c], list(out.iter_input_vars()))
+        self.assertEqual([a1, c], list(out.iter_vars()))
+
+    def test_simple_inline_captures(self):
+        """We should be able to inline captures onto other variables."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+
+        base = QuantumCircuit(inputs=[a, b])
+        base.add_var(c, 255)
+        base.store(a, expr.logic_or(a, b))
+        other = QuantumCircuit(captures=[a, b, c])
+        other.store(c, 254)
+        other.store(b, expr.logic_or(a, b))
+        new = base.compose(other, inline_captures=True)
+
+        expected = QuantumCircuit(inputs=[a, b])
+        expected.add_var(c, 255)
+        expected.store(a, expr.logic_or(a, b))
+        expected.store(c, 254)
+        expected.store(b, expr.logic_or(a, b))
+        self.assertEqual(new, expected)
+
+    def test_can_inline_a_capture_after_remapping(self):
+        """We can use `var_remap` to redefine a capture variable _and then_ inline it in deeply
+        nested scopes.  This is a stress test of capture inlining."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+
+        # We shouldn't be able to inline `qc`'s variable use as-is because it closes over the wrong
+        # variable, but it should work after variable remapping.  (This isn't expected to be super
+        # useful, it's just a consequence of how the order between `var_remap` and `inline_captures`
+        # is defined).
+        base = QuantumCircuit(inputs=[a])
+        qc = QuantumCircuit(declarations=[(c, 255)], captures=[b])
+        qc.store(b, expr.logic_and(b, b))
+        with qc.if_test(expr.logic_not(b)):
+            with qc.while_loop(b):
+                qc.store(b, expr.logic_not(b))
+            # Note that 'c' is captured in this scope, so this is also a test that 'inline_captures'
+            # doesn't do something silly in nested scopes.
+            with qc.switch(c) as case:
+                with case(0):
+                    qc.store(c, expr.bit_and(c, 255))
+                with case(case.DEFAULT):
+                    qc.store(b, expr.equal(c, 255))
+        base.compose(qc, inplace=True, inline_captures=True, var_remap={b: a})
+
+        expected = QuantumCircuit(inputs=[a], declarations=[(c, 255)])
+        expected.store(a, expr.logic_and(a, a))
+        with expected.if_test(expr.logic_not(a)):
+            with expected.while_loop(a):
+                expected.store(a, expr.logic_not(a))
+            # Note that 'c' is not remapped.
+            with expected.switch(c) as case:
+                with case(0):
+                    expected.store(c, expr.bit_and(c, 255))
+                with case(case.DEFAULT):
+                    expected.store(a, expr.equal(c, 255))
+
+        self.assertEqual(base, expected)
+
     def test_rejects_duplicate_bits(self):
         """Test that compose rejects duplicates in either qubits or clbits."""
         base = QuantumCircuit(5, 5)
@@ -911,6 +1023,55 @@ def test_rejects_duplicate_bits(self):
         with self.assertRaisesRegex(CircuitError, "Duplicate clbits"):
             base.compose(attempt, [0, 1], [1, 1])
 
+    def test_cannot_mix_inputs_and_captures(self):
+        """The rules about mixing `input` and `capture` vars should still apply."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        with self.assertRaisesRegex(CircuitError, "circuits with input variables cannot be"):
+            QuantumCircuit(inputs=[a]).compose(QuantumCircuit(captures=[b]))
+        with self.assertRaisesRegex(CircuitError, "circuits to be enclosed with captures cannot"):
+            QuantumCircuit(captures=[a]).compose(QuantumCircuit(inputs=[b]))
+
+    def test_reject_var_naming_collision(self):
+        """We can't have multiple vars with the same name."""
+        a1 = expr.Var.new("a", types.Bool())
+        a2 = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        self.assertNotEqual(a1, a2)
+
+        with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
+            QuantumCircuit(inputs=[a1]).compose(QuantumCircuit(inputs=[a2]))
+        with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
+            QuantumCircuit(captures=[a1]).compose(QuantumCircuit(declarations=[(a2, False)]))
+        with self.assertRaisesRegex(CircuitError, "cannot add.*shadows"):
+            QuantumCircuit(declarations=[(a1, True)]).compose(
+                QuantumCircuit(inputs=[b]), var_remap={b: a2}
+            )
+
+    def test_reject_remap_var_to_bad_type(self):
+        """Can't map a var to a different type."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        qc = QuantumCircuit(inputs=[a])
+        with self.assertRaisesRegex(CircuitError, "mismatched types"):
+            QuantumCircuit().compose(qc, var_remap={a: b})
+        qc = QuantumCircuit(captures=[b])
+        with self.assertRaisesRegex(CircuitError, "mismatched types"):
+            QuantumCircuit().compose(qc, var_remap={b: a})
+
+    def test_reject_inlining_missing_var(self):
+        """Can't inline a var that doesn't exist."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        qc = QuantumCircuit(captures=[a])
+        with self.assertRaisesRegex(CircuitError, "Variable '.*' to be inlined is not in the base"):
+            QuantumCircuit().compose(qc, inline_captures=True)
+
+        # 'a' _would_ be present, except we also say to remap it before attempting the inline.
+        qc = QuantumCircuit(captures=[a])
+        with self.assertRaisesRegex(CircuitError, "Replacement '.*' for variable '.*' is not in"):
+            QuantumCircuit(inputs=[a]).compose(qc, var_remap={a: b}, inline_captures=True)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/dagcircuit/test_compose.py b/test/python/dagcircuit/test_compose.py
index c2862eb200fe..ff5014eacef7 100644
--- a/test/python/dagcircuit/test_compose.py
+++ b/test/python/dagcircuit/test_compose.py
@@ -22,9 +22,10 @@
     WhileLoopOp,
     SwitchCaseOp,
     CASE_DEFAULT,
+    Store,
 )
 from qiskit.circuit.classical import expr, types
-from qiskit.dagcircuit import DAGCircuit
+from qiskit.dagcircuit import DAGCircuit, DAGCircuitError
 from qiskit.converters import circuit_to_dag, dag_to_circuit
 from qiskit.pulse import Schedule
 from qiskit.circuit.gate import Gate
@@ -540,6 +541,91 @@ def test_compose_expr_target(self):
 
         self.assertEqual(dest, circuit_to_dag(expected))
 
+    def test_join_unrelated_dags(self):
+        """This isn't expected to be common, but should work anyway."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+
+        dest = DAGCircuit()
+        dest.add_input_var(a)
+        dest.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        source = DAGCircuit()
+        source.add_declared_var(b)
+        source.add_input_var(c)
+        source.apply_operation_back(Store(b, expr.lift(True)), (), ())
+        dest.compose(source)
+
+        expected = DAGCircuit()
+        expected.add_input_var(a)
+        expected.add_declared_var(b)
+        expected.add_input_var(c)
+        expected.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        expected.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        self.assertEqual(dest, expected)
+
+    def test_join_unrelated_dags_captures(self):
+        """This isn't expected to be common, but should work anyway."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Uint(8))
+
+        dest = DAGCircuit()
+        dest.add_captured_var(a)
+        dest.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        source = DAGCircuit()
+        source.add_declared_var(b)
+        source.add_captured_var(c)
+        source.apply_operation_back(Store(b, expr.lift(True)), (), ())
+        dest.compose(source, inline_captures=False)
+
+        expected = DAGCircuit()
+        expected.add_captured_var(a)
+        expected.add_declared_var(b)
+        expected.add_captured_var(c)
+        expected.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        expected.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        self.assertEqual(dest, expected)
+
+    def test_inline_capture_var(self):
+        """Should be able to append uses onto another DAG."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        dest = DAGCircuit()
+        dest.add_input_var(a)
+        dest.add_input_var(b)
+        dest.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        source = DAGCircuit()
+        source.add_captured_var(b)
+        source.apply_operation_back(Store(b, expr.lift(True)), (), ())
+        dest.compose(source, inline_captures=True)
+
+        expected = DAGCircuit()
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        expected.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        self.assertEqual(dest, expected)
+
+    def test_reject_inline_to_nonexistent_var(self):
+        """Should not be able to inline a variable that doesn't exist."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        dest = DAGCircuit()
+        dest.add_input_var(a)
+        dest.apply_operation_back(Store(a, expr.lift(False)), (), ())
+        source = DAGCircuit()
+        source.add_captured_var(b)
+        with self.assertRaisesRegex(
+            DAGCircuitError, "Variable '.*' to be inlined is not in the base DAG"
+        ):
+            dest.compose(source, inline_captures=True)
+
     def test_compose_calibrations(self):
         """Test that compose carries over the calibrations."""
         dag_cal = QuantumCircuit(1)
diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py
index 62172d084ad3..14033e522c62 100644
--- a/test/python/dagcircuit/test_dagcircuit.py
+++ b/test/python/dagcircuit/test_dagcircuit.py
@@ -439,6 +439,51 @@ def test_copy_empty_like_vars(self):
         dag.add_declared_var(expr.Var.new("d", types.Uint(8)))
         self.assertEqual(dag, dag.copy_empty_like())
 
+    def test_copy_empty_like_vars_captures(self):
+        """Variables can be converted to captures as part of the empty copy."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Bool())
+        d = expr.Var.new("d", types.Uint(8))
+        all_captures = DAGCircuit()
+        for var in [a, b, c, d]:
+            all_captures.add_captured_var(var)
+
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        dag.add_declared_var(c)
+        dag.add_declared_var(d)
+        self.assertEqual(all_captures, dag.copy_empty_like(vars_mode="captures"))
+
+        dag = DAGCircuit()
+        dag.add_captured_var(a)
+        dag.add_captured_var(b)
+        dag.add_declared_var(c)
+        dag.add_declared_var(d)
+        self.assertEqual(all_captures, dag.copy_empty_like(vars_mode="captures"))
+
+    def test_copy_empty_like_vars_drop(self):
+        """Variables can be dropped as part of the empty copy."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Bool())
+        d = expr.Var.new("d", types.Uint(8))
+
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        dag.add_declared_var(c)
+        dag.add_declared_var(d)
+        self.assertEqual(DAGCircuit(), dag.copy_empty_like(vars_mode="drop"))
+
+        dag = DAGCircuit()
+        dag.add_captured_var(a)
+        dag.add_captured_var(b)
+        dag.add_declared_var(c)
+        dag.add_declared_var(d)
+        self.assertEqual(DAGCircuit(), dag.copy_empty_like(vars_mode="drop"))
+
     def test_remove_busy_clbit(self):
         """Classical bit removal of busy classical bits raises."""
         self.dag.apply_operation_back(Measure(), [self.qreg[0]], [self.individual_clbit])
@@ -2255,6 +2300,125 @@ def test_substitute_dag_switch_expr(self):
 
         self.assertEqual(src, expected)
 
+    def test_substitute_dag_vars(self):
+        """Should be possible to replace a node with a DAG acting on the same wires."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Bool())
+
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        dag.add_input_var(c)
+        dag.apply_operation_back(Store(c, expr.lift(False)), (), ())
+        node = dag.apply_operation_back(Store(a, expr.logic_or(expr.logic_or(a, b), c)), (), ())
+        dag.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        replace = DAGCircuit()
+        replace.add_captured_var(a)
+        replace.add_captured_var(b)
+        replace.add_captured_var(c)
+        replace.apply_operation_back(Store(a, expr.logic_or(a, b)), (), ())
+        replace.apply_operation_back(Store(a, expr.logic_or(a, c)), (), ())
+
+        expected = DAGCircuit()
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.add_input_var(c)
+        expected.apply_operation_back(Store(c, expr.lift(False)), (), ())
+        expected.apply_operation_back(Store(a, expr.logic_or(a, b)), (), ())
+        expected.apply_operation_back(Store(a, expr.logic_or(a, c)), (), ())
+        expected.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        dag.substitute_node_with_dag(node, replace, wires={})
+
+        self.assertEqual(dag, expected)
+
+    def test_substitute_dag_if_else_expr_var(self):
+        """Test that substitution works with if/else ops with standalone Vars."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        body_rep = QuantumCircuit(1)
+        body_rep.z(0)
+
+        q_rep = QuantumRegister(1)
+        c_rep = ClassicalRegister(2)
+        replacement = DAGCircuit()
+        replacement.add_qreg(q_rep)
+        replacement.add_creg(c_rep)
+        replacement.add_captured_var(b)
+        replacement.apply_operation_back(XGate(), [q_rep[0]], [])
+        replacement.apply_operation_back(
+            IfElseOp(expr.logic_and(b, expr.equal(c_rep, 1)), body_rep, None), [q_rep[0]], []
+        )
+
+        true_src = QuantumCircuit(1)
+        true_src.x(0)
+        true_src.z(0)
+        false_src = QuantumCircuit(1)
+        false_src.x(0)
+        q_src = QuantumRegister(4)
+        c1_src = ClassicalRegister(2)
+        c2_src = ClassicalRegister(2)
+        src = DAGCircuit()
+        src.add_qreg(q_src)
+        src.add_creg(c1_src)
+        src.add_creg(c2_src)
+        src.add_input_var(a)
+        src.add_input_var(b)
+        node = src.apply_operation_back(
+            IfElseOp(expr.logic_and(b, expr.equal(c1_src, 1)), true_src, false_src), [q_src[2]], []
+        )
+
+        wires = {q_rep[0]: q_src[2], c_rep[0]: c1_src[0], c_rep[1]: c1_src[1]}
+        src.substitute_node_with_dag(node, replacement, wires=wires)
+
+        expected = DAGCircuit()
+        expected.add_qreg(q_src)
+        expected.add_creg(c1_src)
+        expected.add_creg(c2_src)
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.apply_operation_back(XGate(), [q_src[2]], [])
+        expected.apply_operation_back(
+            IfElseOp(expr.logic_and(b, expr.equal(c1_src, 1)), body_rep, None), [q_src[2]], []
+        )
+
+        self.assertEqual(src, expected)
+
+    def test_contract_var_use_to_nothing(self):
+        """The replacement DAG can drop wires."""
+        a = expr.Var.new("a", types.Bool())
+
+        src = DAGCircuit()
+        src.add_input_var(a)
+        node = src.apply_operation_back(Store(a, a), (), ())
+        replace = DAGCircuit()
+        src.substitute_node_with_dag(node, replace, {})
+
+        expected = DAGCircuit()
+        expected.add_input_var(a)
+
+        self.assertEqual(src, expected)
+
+    def test_raise_if_var_mismatch(self):
+        """The DAG can't add more wires."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        src = DAGCircuit()
+        src.add_input_var(a)
+        node = src.apply_operation_back(Store(a, a), (), ())
+
+        replace = DAGCircuit()
+        replace.add_input_var(a)
+        replace.add_input_var(b)
+        replace.apply_operation_back(Store(a, b), (), ())
+
+        with self.assertRaisesRegex(DAGCircuitError, "Cannot replace a node with a DAG with more"):
+            src.substitute_node_with_dag(node, replace, wires={})
+
     def test_raise_if_substituting_dag_modifies_its_conditional(self):
         """Verify that we raise if the input dag modifies any of the bits in node.op.condition."""
 
@@ -2645,6 +2809,55 @@ def test_reject_replace_switch_with_other_resources(self, inplace):
                 node, SwitchCaseOp(expr.lift(cr2), [((1, 3), case.copy())]), inplace=inplace
             )
 
+    @data(True, False)
+    def test_replace_switch_case_standalone_var(self, inplace):
+        """Replace a standalone-Var switch/case with another."""
+        a = expr.Var.new("a", types.Uint(8))
+        b = expr.Var.new("b", types.Uint(8))
+
+        case = QuantumCircuit(1)
+        case.x(0)
+
+        qr = QuantumRegister(1)
+        dag = DAGCircuit()
+        dag.add_qreg(qr)
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        node = dag.apply_operation_back(SwitchCaseOp(a, [((1, 3), case.copy())]), qr, [])
+        dag.substitute_node(
+            node, SwitchCaseOp(expr.bit_and(a, 1), [(1, case.copy())]), inplace=inplace
+        )
+
+        expected = DAGCircuit()
+        expected.add_qreg(qr)
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.apply_operation_back(SwitchCaseOp(expr.bit_and(a, 1), [(1, case.copy())]), qr, [])
+
+        self.assertEqual(dag, expected)
+
+    @data(True, False)
+    def test_replace_store_standalone_var(self, inplace):
+        """Replace a standalone-Var Store with another."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+
+        qr = QuantumRegister(1)
+        dag = DAGCircuit()
+        dag.add_qreg(qr)
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        node = dag.apply_operation_back(Store(a, a), (), ())
+        dag.substitute_node(node, Store(a, expr.logic_not(a)), inplace=inplace)
+
+        expected = DAGCircuit()
+        expected.add_qreg(qr)
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.apply_operation_back(Store(a, expr.logic_not(a)), (), ())
+
+        self.assertEqual(dag, expected)
+
 
 class TestReplaceBlock(QiskitTestCase):
     """Test replacing a block of nodes in a DAG."""
@@ -2729,6 +2942,34 @@ def test_replace_control_flow_block(self):
 
         self.assertEqual(dag, expected)
 
+    def test_contract_stores(self):
+        """Test that contraction over nodes with `Var` wires works."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        c = expr.Var.new("c", types.Bool())
+
+        dag = DAGCircuit()
+        dag.add_input_var(a)
+        dag.add_input_var(b)
+        dag.add_input_var(c)
+        dag.apply_operation_back(Store(c, expr.lift(False)), (), ())
+        nodes = [
+            dag.apply_operation_back(Store(a, expr.logic_or(a, b)), (), ()),
+            dag.apply_operation_back(Store(a, expr.logic_or(a, c)), (), ()),
+        ]
+        dag.apply_operation_back(Store(b, expr.lift(True)), (), ())
+        dag.replace_block_with_op(nodes, Store(a, expr.logic_or(expr.logic_or(a, b), c)), {})
+
+        expected = DAGCircuit()
+        expected.add_input_var(a)
+        expected.add_input_var(b)
+        expected.add_input_var(c)
+        expected.apply_operation_back(Store(c, expr.lift(False)), (), ())
+        expected.apply_operation_back(Store(a, expr.logic_or(expr.logic_or(a, b), c)), (), ())
+        expected.apply_operation_back(Store(b, expr.lift(True)), (), ())
+
+        self.assertEqual(dag, expected)
+
 
 class TestDagProperties(QiskitTestCase):
     """Test the DAG properties."""

From a78c94144c59c504a8f36ab9786a404a572aff20 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Wed, 1 May 2024 23:49:24 +0100
Subject: [PATCH 051/179] Add minimal support for standalone `Var` to
 visualisers (#12307)

* Add minimal support for standalone `Var` to visualisers

This adds best-effort only support to the visualisers for handling
stand-alone `Var` nodes.  Most of the changes are actually in `qasm3`,
since the visualisers use internal details of that to handle the nodes.

This commit decouples the visualisers _slightly_ more from the inner
workings of the OQ3 exporter by having them manage their own
variable-naming contexts and using the encapsulated `_ExprBuilder`,
rather than poking into random internals of the full circuit exporter.
This is necessary to allow the OQ3 exporter to expand to support these
variables itself, and also for the visualisers, since variables may now
be introduced in inner scopes.

This commit does not attempt to solve many of the known problems around
zero-operand "gates", of which `Store` is one, just leaving it un-drawn.
Printing to OpenQASM 3 is possibly a better visualisation strategy for
large dynamic circuits for the time being.

* Fix typos

Co-authored-by: Matthew Treinish 

---------

Co-authored-by: Matthew Treinish 
---
 qiskit/qasm3/ast.py                           |  11 +++
 qiskit/qasm3/exporter.py                      |  20 +++--
 qiskit/qasm3/printer.py                       |   8 ++
 qiskit/visualization/circuit/matplotlib.py    |  56 ++++++++++---
 qiskit/visualization/circuit/text.py          |  56 +++++++++----
 .../visualization/test_circuit_text_drawer.py |  79 +++++++++++++++++-
 .../references/if_else_standalone_var.png     | Bin 0 -> 17114 bytes
 .../references/switch_standalone_var.png      | Bin 0 -> 22166 bytes
 .../circuit/test_circuit_matplotlib_drawer.py |  55 +++++++++++-
 9 files changed, 243 insertions(+), 42 deletions(-)
 create mode 100644 test/visual/mpl/circuit/references/if_else_standalone_var.png
 create mode 100644 test/visual/mpl/circuit/references/switch_standalone_var.png

diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py
index fd7aa11d4819..7674eace89db 100644
--- a/qiskit/qasm3/ast.py
+++ b/qiskit/qasm3/ast.py
@@ -123,6 +123,10 @@ class FloatType(ClassicalType, enum.Enum):
     OCT = 256
 
 
+class BoolType(ClassicalType):
+    """Type information for a Boolean."""
+
+
 class IntType(ClassicalType):
     """Type information for a signed integer."""
 
@@ -130,6 +134,13 @@ def __init__(self, size: Optional[int] = None):
         self.size = size
 
 
+class UintType(ClassicalType):
+    """Type information for an unsigned integer."""
+
+    def __init__(self, size: Optional[int] = None):
+        self.size = size
+
+
 class BitType(ClassicalType):
     """Type information for a single bit."""
 
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index d8d3a42087a6..329d09830ac5 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -1058,6 +1058,14 @@ def _lift_condition(condition):
     return expr.lift_legacy_condition(condition)
 
 
+def _build_ast_type(type_: types.Type) -> ast.ClassicalType:
+    if type_.kind is types.Bool:
+        return ast.BoolType()
+    if type_.kind is types.Uint:
+        return ast.UintType(type_.width)
+    raise RuntimeError(f"unhandled expr type '{type_}'")  # pragma: no cover
+
+
 class _ExprBuilder(expr.ExprVisitor[ast.Expression]):
     __slots__ = ("lookup",)
 
@@ -1069,7 +1077,7 @@ def __init__(self, lookup):
         self.lookup = lookup
 
     def visit_var(self, node, /):
-        return self.lookup(node.var)
+        return self.lookup(node) if node.standalone else self.lookup(node.var)
 
     def visit_value(self, node, /):
         if node.type.kind is types.Bool:
@@ -1080,14 +1088,8 @@ def visit_value(self, node, /):
 
     def visit_cast(self, node, /):
         if node.implicit:
-            return node.accept(self)
-        if node.type.kind is types.Bool:
-            oq3_type = ast.BoolType()
-        elif node.type.kind is types.Uint:
-            oq3_type = ast.BitArrayType(node.type.width)
-        else:
-            raise RuntimeError(f"unhandled cast type '{node.type}'")
-        return ast.Cast(oq3_type, node.operand.accept(self))
+            return node.operand.accept(self)
+        return ast.Cast(_build_ast_type(node.type), node.operand.accept(self))
 
     def visit_unary(self, node, /):
         return ast.Unary(ast.Unary.Op[node.op.name], node.operand.accept(self))
diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py
index 94d12a7ecff6..ba253144a168 100644
--- a/qiskit/qasm3/printer.py
+++ b/qiskit/qasm3/printer.py
@@ -204,11 +204,19 @@ def _visit_CalibrationGrammarDeclaration(self, node: ast.CalibrationGrammarDecla
     def _visit_FloatType(self, node: ast.FloatType) -> None:
         self.stream.write(f"float[{self._FLOAT_WIDTH_LOOKUP[node]}]")
 
+    def _visit_BoolType(self, _node: ast.BoolType) -> None:
+        self.stream.write("bool")
+
     def _visit_IntType(self, node: ast.IntType) -> None:
         self.stream.write("int")
         if node.size is not None:
             self.stream.write(f"[{node.size}]")
 
+    def _visit_UintType(self, node: ast.UintType) -> None:
+        self.stream.write("uint")
+        if node.size is not None:
+            self.stream.write(f"[{node.size}]")
+
     def _visit_BitType(self, _node: ast.BitType) -> None:
         self.stream.write("bit")
 
diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py
index 8d83fecb8969..c547846acc5b 100644
--- a/qiskit/visualization/circuit/matplotlib.py
+++ b/qiskit/visualization/circuit/matplotlib.py
@@ -33,6 +33,7 @@
     IfElseOp,
     ForLoopOp,
     SwitchCaseOp,
+    CircuitError,
 )
 from qiskit.circuit.controlflow import condition_resources
 from qiskit.circuit.classical import expr
@@ -46,7 +47,8 @@
     XGate,
     ZGate,
 )
-from qiskit.qasm3.exporter import QASM3Builder
+from qiskit.qasm3 import ast
+from qiskit.qasm3.exporter import _ExprBuilder
 from qiskit.qasm3.printer import BasicPrinter
 
 from qiskit.circuit.tools.pi_check import pi_check
@@ -393,7 +395,7 @@ def draw(self, filename=None, verbose=False):
             matplotlib_close_if_inline(mpl_figure)
             return mpl_figure
 
-    def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data, builder=None):
+    def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data):
         """Compute the layer_widths for the layers"""
 
         layer_widths = {}
@@ -482,18 +484,41 @@ def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data, build
                     if (isinstance(op, SwitchCaseOp) and isinstance(op.target, expr.Expr)) or (
                         getattr(op, "condition", None) and isinstance(op.condition, expr.Expr)
                     ):
-                        condition = op.target if isinstance(op, SwitchCaseOp) else op.condition
-                        if builder is None:
-                            builder = QASM3Builder(
-                                outer_circuit,
-                                includeslist=("stdgates.inc",),
-                                basis_gates=("U",),
-                                disable_constants=False,
-                                allow_aliasing=False,
+
+                        def lookup_var(var):
+                            """Look up a classical-expression variable or register/bit in our
+                            internal symbol table, and return an OQ3-like identifier."""
+                            # We don't attempt to disambiguate anything like register/var naming
+                            # collisions; we already don't really show classical variables.
+                            if isinstance(var, expr.Var):
+                                return ast.Identifier(var.name)
+                            if isinstance(var, ClassicalRegister):
+                                return ast.Identifier(var.name)
+                            # Single clbit.  This is not actually the correct way to lookup a bit on
+                            # the circuit (it doesn't handle bit bindings fully), but the mpl
+                            # drawer doesn't completely track inner-outer _bit_ bindings, only
+                            # inner-indices, so we can't fully recover the information losslessly.
+                            # Since most control-flow uses the control-flow builders, we should
+                            # decay to something usable most of the time.
+                            try:
+                                register, bit_index, reg_index = get_bit_reg_index(
+                                    outer_circuit, var
+                                )
+                            except CircuitError:
+                                # We failed to find the bit due to binding problems - fall back to
+                                # something that's probably wrong, but at least disambiguating.
+                                return ast.Identifier(f"bit{wire_map[var]}")
+                            if register is None:
+                                return ast.Identifier(f"bit{bit_index}")
+                            return ast.SubscriptedIdentifier(
+                                register.name, ast.IntegerLiteral(reg_index)
                             )
-                            builder.build_classical_declarations()
+
+                        condition = op.target if isinstance(op, SwitchCaseOp) else op.condition
                         stream = StringIO()
-                        BasicPrinter(stream, indent="  ").visit(builder.build_expression(condition))
+                        BasicPrinter(stream, indent="  ").visit(
+                            condition.accept(_ExprBuilder(lookup_var))
+                        )
                         expr_text = stream.getvalue()
                         # Truncate expr_text so that first gate is no more than about 3 x_index's over
                         if len(expr_text) > self._expr_len:
@@ -570,7 +595,7 @@ def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data, build
 
                         # Recursively call _get_layer_widths for the circuit inside the ControlFlowOp
                         flow_widths = flow_drawer._get_layer_widths(
-                            node_data, flow_wire_map, outer_circuit, glob_data, builder
+                            node_data, flow_wire_map, outer_circuit, glob_data
                         )
                         layer_widths.update(flow_widths)
 
@@ -1243,6 +1268,11 @@ def _condition(self, node, node_data, wire_map, outer_circuit, cond_xy, glob_dat
             self._ax.add_patch(box)
             xy_plot.append(xy)
 
+        if not xy_plot:
+            # Expression that's only on new-style `expr.Var` nodes, and doesn't need any vertical
+            # line drawing.
+            return
+
         qubit_b = min(node_data[node].q_xy, key=lambda xy: xy[1])
         clbit_b = min(xy_plot, key=lambda xy: xy[1])
 
diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py
index 1e6137275a9f..abefe5511775 100644
--- a/qiskit/visualization/circuit/text.py
+++ b/qiskit/visualization/circuit/text.py
@@ -20,7 +20,7 @@
 import collections
 import sys
 
-from qiskit.circuit import Qubit, Clbit, ClassicalRegister
+from qiskit.circuit import Qubit, Clbit, ClassicalRegister, CircuitError
 from qiskit.circuit import ControlledGate, Reset, Measure
 from qiskit.circuit import ControlFlowOp, WhileLoopOp, IfElseOp, ForLoopOp, SwitchCaseOp
 from qiskit.circuit.classical import expr
@@ -28,8 +28,9 @@
 from qiskit.circuit.library.standard_gates import IGate, RZZGate, SwapGate, SXGate, SXdgGate
 from qiskit.circuit.annotated_operation import _canonicalize_modifiers, ControlModifier
 from qiskit.circuit.tools.pi_check import pi_check
-from qiskit.qasm3.exporter import QASM3Builder
+from qiskit.qasm3 import ast
 from qiskit.qasm3.printer import BasicPrinter
+from qiskit.qasm3.exporter import _ExprBuilder
 
 from ._utils import (
     get_gate_ctrl_text,
@@ -748,7 +749,6 @@ def __init__(
 
         self._nest_depth = 0  # nesting depth for control flow ops
         self._expr_text = ""  # expression text to display
-        self._builder = None  # QASM3Builder class instance for expressions
 
         # Because jupyter calls both __repr__ and __repr_html__ for some backends,
         # the entire drawer can be run twice which can result in different output
@@ -1306,25 +1306,44 @@ def add_control_flow(self, node, layers, wire_map):
         if (isinstance(node.op, SwitchCaseOp) and isinstance(node.op.target, expr.Expr)) or (
             getattr(node.op, "condition", None) and isinstance(node.op.condition, expr.Expr)
         ):
+
+            def lookup_var(var):
+                """Look up a classical-expression variable or register/bit in our internal symbol
+                table, and return an OQ3-like identifier."""
+                # We don't attempt to disambiguate anything like register/var naming collisions; we
+                # already don't really show classical variables.
+                if isinstance(var, expr.Var):
+                    return ast.Identifier(var.name)
+                if isinstance(var, ClassicalRegister):
+                    return ast.Identifier(var.name)
+                # Single clbit.  This is not actually the correct way to lookup a bit on the
+                # circuit (it doesn't handle bit bindings fully), but the text drawer doesn't
+                # completely track inner-outer _bit_ bindings, only inner-indices, so we can't fully
+                # recover the information losslessly.  Since most control-flow uses the control-flow
+                # builders, we should decay to something usable most of the time.
+                try:
+                    register, bit_index, reg_index = get_bit_reg_index(self._circuit, var)
+                except CircuitError:
+                    # We failed to find the bit due to binding problems - fall back to something
+                    # that's probably wrong, but at least disambiguating.
+                    return ast.Identifier(f"_bit{wire_map[var]}")
+                if register is None:
+                    return ast.Identifier(f"_bit{bit_index}")
+                return ast.SubscriptedIdentifier(register.name, ast.IntegerLiteral(reg_index))
+
             condition = node.op.target if isinstance(node.op, SwitchCaseOp) else node.op.condition
-            if self._builder is None:
-                self._builder = QASM3Builder(
-                    self._circuit,
-                    includeslist=("stdgates.inc",),
-                    basis_gates=("U",),
-                    disable_constants=False,
-                    allow_aliasing=False,
-                )
-                self._builder.build_classical_declarations()
+            draw_conditional = bool(node_resources(condition).clbits)
             stream = StringIO()
-            BasicPrinter(stream, indent="  ").visit(self._builder.build_expression(condition))
+            BasicPrinter(stream, indent="  ").visit(condition.accept(_ExprBuilder(lookup_var)))
             self._expr_text = stream.getvalue()
             # Truncate expr_text at 30 chars or user-set expr_len
             if len(self._expr_text) > self.expr_len:
                 self._expr_text = self._expr_text[: self.expr_len] + "..."
+        else:
+            draw_conditional = not isinstance(node.op, ForLoopOp)
 
         # # Draw a left box such as If, While, For, and Switch
-        flow_layer = self.draw_flow_box(node, wire_map, CF_LEFT)
+        flow_layer = self.draw_flow_box(node, wire_map, CF_LEFT, conditional=draw_conditional)
         layers.append(flow_layer.full_layer)
 
         # Get the list of circuits in the ControlFlowOp from the node blocks
@@ -1351,7 +1370,9 @@ def add_control_flow(self, node, layers, wire_map):
 
             if circ_num > 0:
                 # Draw a middle box such as Else and Case
-                flow_layer = self.draw_flow_box(node, flow_wire_map, CF_MID, circ_num - 1)
+                flow_layer = self.draw_flow_box(
+                    node, flow_wire_map, CF_MID, circ_num - 1, conditional=False
+                )
                 layers.append(flow_layer.full_layer)
 
             _, _, nodes = _get_layered_instructions(circuit, wire_map=flow_wire_map)
@@ -1380,14 +1401,13 @@ def add_control_flow(self, node, layers, wire_map):
                 layers.append(flow_layer2.full_layer)
 
         # Draw the right box for End
-        flow_layer = self.draw_flow_box(node, flow_wire_map, CF_RIGHT)
+        flow_layer = self.draw_flow_box(node, flow_wire_map, CF_RIGHT, conditional=False)
         layers.append(flow_layer.full_layer)
 
-    def draw_flow_box(self, node, flow_wire_map, section, circ_num=0):
+    def draw_flow_box(self, node, flow_wire_map, section, circ_num=0, conditional=False):
         """Draw the left, middle, or right of a control flow box"""
 
         op = node.op
-        conditional = section == CF_LEFT and not isinstance(op, ForLoopOp)
         depth = str(self._nest_depth)
         if section == CF_LEFT:
             etext = ""
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
index 9b34257f567c..5f72a7d1bbbc 100644
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -12,6 +12,9 @@
 
 """circuit_drawer with output="text" draws a circuit in ascii art"""
 
+# Sometimes we want to test long-lined output.
+# pylint: disable=line-too-long
+
 import pathlib
 import os
 import tempfile
@@ -37,7 +40,7 @@
 from qiskit.visualization import circuit_drawer
 from qiskit.visualization.circuit import text as elements
 from qiskit.providers.fake_provider import GenericBackendV2
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import (
     HGate,
     U2Gate,
@@ -6316,6 +6319,80 @@ def test_switch_with_expression(self):
             expected,
         )
 
+    def test_nested_if_else_op_var(self):
+        """Test if/else with standalone Var."""
+        expected = "\n".join(
+            [
+                "     ┌───────── ┌────────────────       ───────┐ ┌──────────────────── ┌───┐ ───────┐  ───────┐ ",
+                "q_0: ┤          ┤                 ──■──        ├─┤ If-1 c && a == 128  ┤ H ├  End-1 ├─        ├─",
+                "     │ If-0 !b  │ If-1 b == c[0]  ┌─┴─┐  End-1 │ └──────────────────── └───┘ ───────┘   End-0 │ ",
+                "q_1: ┤          ┤                 ┤ X ├        ├──────────────────────────────────────        ├─",
+                "     └───────── └───────╥──────── └───┘ ───────┘                                       ───────┘ ",
+                "                    ┌───╨────┐                                                                  ",
+                "c: 2/═══════════════╡ [expr] ╞══════════════════════════════════════════════════════════════════",
+                "                    └────────┘                                                                  ",
+            ]
+        )
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        b = qc.add_var("b", False)
+        qc.store(a, 128)
+        with qc.if_test(expr.logic_not(b)):
+            # Mix old-style and new-style.
+            with qc.if_test(expr.equal(b, qc.clbits[0])):
+                qc.cx(0, 1)
+            c = qc.add_var("c", b)
+            with qc.if_test(expr.logic_and(c, expr.equal(a, 128))):
+                qc.h(0)
+
+        actual = str(qc.draw("text", fold=-1, initial_state=False))
+        self.assertEqual(actual, expected)
+
+    def test_nested_switch_op_var(self):
+        """Test switch with standalone Var."""
+        expected = "\n".join(
+            [
+                "     ┌───────────── ┌──────────── ┌──────────── ┌────────────      »",
+                "q_0: ┤              ┤             ┤             ┤             ──■──»",
+                "     │ Switch-0 ~a  │ Case-0 (0)  │ Switch-1 b  │ Case-1 (2)  ┌─┴─┐»",
+                "q_1: ┤              ┤             ┤             ┤             ┤ X ├»",
+                "     └───────────── └──────────── └──────────── └──────────── └───┘»",
+                "c: 2/══════════════════════════════════════════════════════════════»",
+                "                                                                   »",
+                "«     ┌──────────────── ┌───┐ ───────┐ ┌──────────────── ┌──────── ┌───┐»",
+                "«q_0: ┤                 ┤ X ├        ├─┤                 ┤ If-1 c  ┤ H ├»",
+                "«     │ Case-1 default  └─┬─┘  End-1 │ │ Case-0 default  └──────── └───┘»",
+                "«q_1: ┤                 ──■──        ├─┤                 ───────────────»",
+                "«     └────────────────       ───────┘ └────────────────                »",
+                "«c: 2/══════════════════════════════════════════════════════════════════»",
+                "«                                                                       »",
+                "«      ───────┐  ───────┐ ",
+                "«q_0:   End-1 ├─        ├─",
+                "«      ───────┘   End-0 │ ",
+                "«q_1: ──────────        ├─",
+                "«                ───────┘ ",
+                "«c: 2/════════════════════",
+                "«                         ",
+            ]
+        )
+
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        b = qc.add_var("b", expr.lift(5, a.type))
+        with qc.switch(expr.bit_not(a)) as case:
+            with case(0):
+                with qc.switch(b) as case2:
+                    with case2(2):
+                        qc.cx(0, 1)
+                    with case2(case2.DEFAULT):
+                        qc.cx(1, 0)
+            with case(case.DEFAULT):
+                c = qc.add_var("c", expr.equal(a, b))
+                with qc.if_test(c):
+                    qc.h(0)
+        actual = str(qc.draw("text", fold=80, initial_state=False))
+        self.assertEqual(actual, expected)
+
 
 class TestCircuitAnnotatedOperations(QiskitVisualizationTestCase):
     """Test AnnotatedOperations and other non-Instructions."""
diff --git a/test/visual/mpl/circuit/references/if_else_standalone_var.png b/test/visual/mpl/circuit/references/if_else_standalone_var.png
new file mode 100644
index 0000000000000000000000000000000000000000..6266a0caeb00ee4dc8d2aa8bf53c197a8ff76d51
GIT binary patch
literal 17114
zcmeIacTkh-|1BCo#0G9fKvZBWARtwwBVa?N_ufQ`(vcQAxK-SWf^-2Xg0#?kCrFVd
zASHwriu95oy_5TSzrQnQZks#j%$z^&ow?5JiD2^Pd7t-F)@QBt?04#_k7!R_I)y@^
zXcZnm&_tmqmr*DR>l4S}H)2!W?Nw%PfP{bu-`~&O+?-vd1O*-b`x^wDT%HNCak9I^MNT?DHgH9un9d{rQ9MiPg|E8z
zNa4XeKLwQkS5VhLUOr!Z$pSAzH~;?+{%3P$i6lz(7em5b3OqbTX=zHau_S?vhQc9xDfD%f
zA)C3#glHZ+$HZ!-d(>9?d~38D5l-rXl$hgfzlk$NEnUj0db+kjMSa3iF%e5eE!0m0
z80E}%ahW%;tOE<5uWDPnTs%Uielx{HW=CPl-eCYo=a77s;!7GROJ
z=$qt-?=2>nocolNAERCU{;QPE8Tr?bO&J>(Lc?6T(y?8GZaW`Tcs?+oc4CqhO)(>R
zFH=a%xfpRbA$d=6d$G&!PFi(UJxj_-I*R#3x~?0qQXM+7@r?2+OXAc$Pbb&dt8y#7
zG~0uo*-zEZ^-^3gQ|A;Et*T4Q`@H_z=#}(K7WlMJ=-PEGpOsZ*b>yiw+hQA~(cJ<%
z&lF75x1~6JMTTKCOZj5CMa^+kq;iCZJMJiW!SOVa#5VP)@}WLjutBQ*)h*`Uy+d8A
zndoINWqlfT!~OE3H2(4r5jb1-jw$*<4eJw2BrEou)GItT&$)7_oEgzU9+cAUuRE=^
z#=dNyTyk_1)_Lt7$@@(B+hN9g@)y;PqH_Zj61bz>03P$1ZGiw|-FJ6~Bzzx8dDr|Q
zhMg;Nt1|#U6poqXrs}A
zNx{6wx;i?#G&~fm&U;+Z9&J~Vs_}?->9Up>-F~djUuv>yShy!6E|J^q+E}3xnbA?~Te$GD0%43got@7;|+t
z{Rzf~>|6;c%G3OY*Oq=;(Umh3q&k)+HH2$U%v&|bMPxVUlC*SthbTvC4*YNylM(Ni
z2ODUkDCCu-y$$VXWHX8b5?m^3$A1`LdZ&x7?Hdf){c}FxFm>X1qxu}aCG&f7UK&5c
z9Z7f3u4ls%R#}a2h?G=(Rhtvf1Y{_pnvP`@57`$Tl?+XarM|e|P<8st=tgy8fq(*w
zX=PFYoz`g#OWu;4&%+y*CDM*F>DTrp_VGL|<|dliEyvFfP#9|`Xxw+3`9()po|aTx
ztq*t+4F0k`3Fan6H5Yr@Qb;0BelG{*(4Rc>WWJfy>I%1)jxWuusBK|Y(`%T
zl{0R&yAytiuCDBx6-DNqMLBOH^Qq%|OMUJFxo@Unc?nWP&sXNHE-KP^MyG*-@54D%;zSg>WwyUG9zK2gRK#^w*iwZT$>(O&HRQT>n)ZgTPuvtfP%xiDTYG`Iy_U#|)-yjfmh>~?
zpHl$=yBQ6#;;zEQ2?m;BCzhS-8U|RZTA%A1=oedmbgWvqUTzZgGvSocK9ruS@w@Uz
zk_B4o03P{
zTP5V)ii(Kz*S^tKKOm>3Oo^iE4Nfd`>xnszayJUCB^=6lty%iSn_qmBcqOQJip<8q
zl23uMdimdaj_aUCo*QY@;8}NK6bCg8jZ%1`=kJp;yVDU{e!hNw1WhCPda~RNDN`y`
zJLOB3nfEa=qIw{nX(k`WnsLH^-GjKfITB~jbRylRq^g679dq8Hc#js$XInUY+*7;dUMqYr$h@Lb
z6Lm-9#In^ygqmy*d+6JYJNzaXl#(cIBt)`OBBHt5@ob!YvE+OP=>!li411-EFAi(8)74Nb=vyYG`bXN=R^U
z3WapEszkMi<=4sOyfrqd|SZ!g$PkeuM
znjkJAq5tYQZ9D$Q3F*M_0z(IH-ye+o3TbJpM@5IjeiI+)F_b6?`j>>0WlrOHf^|Fh
z!Y{D0<|#y6XYf)EX5?Qk_w)0^)p)tS-G{7FPJI-rtF0Y*aCpeT%$)U-ipI7hn!k&O
zHSp2CW5)}Y9bTNxVQsBtQr{k>dSOCGhnAtEY8Y8vWVS&1yYxzp1=bHr4mUSuq>*8dvecg7f-y+vUN+IJtnjhap$8#2kh*;XBigBstn_OK;q~
zS=fKLlb)c%NkjDV@=}!btLi^YeeUH|NjK7+sM2TSPc!o)t4TR`a3@A{`T9X5>&txx#5~ZeFC{ZoPKuWo_iv@=)qjOQ@1mz&)E6jjqPVUk=MscbA;7hh-tG
zGMG=F{>s%aym#*0x$rJIX4#zyDk7m;Ē=bJ*ra^DL@-kUcyaM^l-?sD5WhvXst
z5yt0oo__-KobQpjha+wA+0p37FSkz8Uw_`%7*O@~TI4%L+O*^J31h@Tt_-_n8M3=K
zHU?9M{?|0qqqfmO<&wB=RqmhsUN*Yghq;G#jL!q+63`n%8yc$F=l)!Xs`Yc*`(b3f
z_jxI=Qr9G#;jnV0+U4D8%&t*m(8oy4pIH)u_=RtmFdE4+GBUMytlx)*>UnxrXxSc!
z&lbe=ngtxnROw*-51*2)N^Sbavu{eAT1LX3!)CC+e5=6!W%JV>i%^;C3WlY&QJR{X
z0+o)anLxcKJ^PWFW{<|hC{Uws;Q4c@ekmBwgZC7My!-eO2i1#HA;>%v(sH*fNybMP
zd$1PFv9Pzq5-PAc-*Z36oSTd5(aoDTIeAzDTZ+B)v8cfCzYAhCRlCN`vMenxILY>Z
zV7_I!kF^UDw;wF1SHSpeANid(Fwbsz{pQUO_WkbM5$eXLnhoK_a?cej6bk=@&WGm>
z{FBoPJP(MRwY9YyZsgCOFLowK@;y
zSJOx>=&8`&mf+JR@O6(@gP9_$0xl5uk0PSB=GrKUed#J%wO36D>gU?;DH?_t(j7gV
zD_Z+9yyBBDmwQAQ^RP|g47=o`El20uGg)?6Sh$vJyc3yBmJxZhI=*t_*HJyy+4+wz
zs1kJ9CwM-5IJ59|l_b!3fsXa|-$rS_RSttMlhggpiZkx`JbCiu<uSBt{6*}Hu{y(v-?s@)oYhlKaWbK+>_t#Mg+1cp#$^6K_%A>-Re_E&_O8B{4E
ze`JzHTx|ok3Byen%vn%>#3`%km1BjQ{r9KaBC6Kfd2zKq=#N(L6Kn}xwbi&JOM;=?(W`U@={&CDbuIbC9AUT`L?
zx3)gsC!V&ek#;dB?a6J^sIS(nE7MRPkMs|k>VKQxZ*}erOvgh)ZwU(5Rlj7yQkF{b
zrdrC%AE2`6sUSmj_o<_7C=!^0!9bP;X64JSlY;(H$vs=qn%h&o*-8@w6&hsoPhD
zZ%Ow3Nq&UY(ZozC*)YxS_Bd>8v_5AJ(s+oYKqwQiIXiB|VS{Z8&Fcx?dOYs~DNg-O5E>;=%llALvmBN}>fm(Tbl1-Ma
z&VgW6etG%V@8S;@-kx>py|rC_J%B9GgDKkZ4q2C?n9{CtybsmdhhmRyG_TF=ci(-7KgL1om#$i
zMB;VxB>u&p=${}}&R@ZTdLnSlgGtJ5{*$Jb7N^41t5-2*SpTp4x)^N*GymN*jCNKE
zzfoBxG}=?Nv^r1;;d{6Q1qEyON!7MPMQPdX;%~e+7qiroWJv<4qdug{Im?Sd=g*%X
zHBpSM+8VaAx)^k(k$ZK0vF8z6>9%<~L;S&Ejk`}$kfHEl73Ii}G50su8iUb2f1v{R
z;g08%*72#S!HI&y=_;3r!=*~84?k)mVNDv9eDn6LU402NkuU6JwDz8`c(V{;M506-3EvE9+ZE|ZM)%2;jHLVCP*sjUtKrjALq2d+Je
z*F>v8VR;;D7Amv#88+_kfB^D6IVuQGQNKMm_k~{h$;nCBD;!SGEVnuMw5gR*INcSA
zACDs#}bKlAH|
z6SnrwK8zAL;Hpe~h^5}^o#Lf-cmb7IVR@*--jM9XeI$ed^x}{ZCXSR>7rA|o#M$bM
z*ZDC3XnVxwr!8tX&_9rCRO-3w$EZ#Mt?QhN#<~)Zw;U&f`Tnu{~`_*bB(+8
zus;gPgiXR2R~G&Fz?_QF$+lge?Tiz#3HRTfVe;FZi4oXa2n=U0sQ!h0G)VCo4^;(M
zWToT>H?@^b|MvCk`Lr0*UIc2VtHf7T-usL@I@~X{XrZfxI(JFj@e7Adf2Kxzd;5)z
z&(Nt@;K3>i3x-04sL_`N9
znnGD+L>z`iKC;P;`JZbDl{?aJYioP|?p@DJjG2JKB}tcWIl9@5eEP}XzH#VeX{P-Q
z5K9*Me17U)M~q-hr*%Z9HLqJFbu%n_oLfrD@aX8scC4mSmVDrfj?ot2EhBCf0JxwVl+GUFb2ix2L}fi7#PxM*nHK8hM3I6`kqRXd-l4s
z4VA)^l9IGSUs%HZF8=!Zs-dCb!e4(SmpP1h_pd`#VNTJ}3AkW0Gc$e7O~TJKs*{fn
zqoShHVks#zU)ReHa5+Buj*S`GFoSp@wrABvVMEq@TH-**Z#eB?OF+X#nf*Tr{_H#_DH~c
zIfO-;LqX5f%c^cGPSg$qnX^UY#)h!T<-uQhBqa?X*t7v=KtaFBZ&Ibr%*@P0>jh+i
z##=g;jm7MI+tyE4F|ewzP&8EE8#it!MqF3XgqUZzbjdKK3V@vZCUHcsF^E>n!=p@g
zfB6>eC6UKwew$Iy1k;0A?Sk+QHVL=AG{d!_N%@RG)5?bOf;^45+g?Sm$t5;@*J}^A
zM!a_>-xAqZ17pt(PQG|*VNq&7Bn;r=PPoX^rW2wL!`k%MmH#e~Hh_V|℘dCl#qO
zlCHCZ8NnB3qx4L92EtR_mj*h~_l7>-wAg?;$>3#-&oe27%)D1VH#4(vw7;TfY5C2A
zG#V>-aNu*SFAOHA$5L{X+fE2H^FQzm^Q$kGS#6@1@>=^=W%N+w|kdjlch{_Ye5_
z`6dFWth>G#SUy2-FO`*aeUVJ9be+Sv&UNjYR@ZuO_MR53Nsck~R^ZjmDJYlQ?AMAD
zwo;76QNgIj!kPhfV>j>U65K8fRa)@0`k*~AocmtY+a>J1G4H$0<~@J4GxpXaSj?t8
z)qVnc%piyN&(SUY$GtnNQ!I(7Jtz52aYCU5JXEP~016l`X7P1V{{tw3t8LZoSU^C)
zzTPr-5+rQ2pjjT&uFCpyzTW1f`<0cIizDUvlvGqmxrFUik@4B;htYSryID={P`jMq
z5f5-O^EmA#Zca{lxEIa+EuyG-!!hK&<)bdax_QPnY;8$W?wCZcS?08i3^TX5pK@ke
z5Z&!CG0t@*Y(gVq+;y$qcQ?AEo2`R{5@63U-}O$)ktJMqhJZ=c{D6^@4%7kNTtg^r
z-U=`WUAkqh9E+otIF0O(d1DZ@tk7j~M?M8$71#@;HK0PR5uAP{zvN?73R7|z$TuUW
z;F-CVp_0OUyRIZl8b8{ZI;)@w07KF3;LV-i^RKZ#fBvL=Nj=lC(3cSv7iX9<*WH_@
zRO-H@>p-Z|`FL4;V7Sx{SLym}lSD9D8pzdp@+8?~Wi0p4A7^=ad4=#X|9uA*Szmp*
z!;Oe*?ChJ(OlFzrm9a}wZkf@12G^k76USDE)kGfh1~yH
znT<~aIR>fJry2iiit*bTdWO_eNC=&34+n+bKng0$QbS~iX<4MSVT2@cU*;C0ln#8#
zOPX2g>+OwgS6(1UkxzP4DP!
zAw^dQ#pXEewh&f@1a#*1I<$n{4KPechs05qB8=R^`4loP;J+
zqwy_l0XbjqzgN`Vhe6^JvTJR1wFjwtYO=fET8Cve
z-KBwaL;g(=dr$eV6M*J;gqEvfB}P%(j$qmweme$A|NsZ~UMLei3FWgJK*0
z4VD`-|7xe9C>wBU2UAxl6hR<3Y^PhpVn(@cKLSv8@7}!B+-m9-npZ-TMb1V1M#ms@J>8N{j{6`J|G3yd!=^UjeNM>(b1vQyPI`7
z$o%<4{Y&|Q6WvyY`uzZt_>C*nVWW73LJyyIe+9N?{-gNVui|dmY$#pBBm0|689pnu
zdVuotARMBdO3ELFvKkgXedCw7M-AKGqi5!q21Ubm=g*{kp7rmU8O}zb$e5UCPQk$Y
zt<7`t@Ei~Tny;9G2
zVp%8`h8(2`F0}y&yS0z=K;r7NKm72Ee6%fx%hb3J`P~P#`n}Bi*T1C!k0Ut7wAR}O
zes5Ue{ME6VSVrmkfZR!NHRopxMlzNFg$C&50RnnF7p5>kerj0qQT0HciIF8+Uxu0<
z63DQ-w9LW}s?jS@iE7i3JBRMg>;6%w9H~d+zUv>WJcyVu_D2vf)>1EizdqU@C*Qb!
zJ^hyz)BeUns{i5M!mG2gzrMesK|tv4?k>{)4J%#Js@xV*XfKP+4?Ro5t#``_DpbLq
zmN-w|li6P`9xii;_ug908XYy}H>)#7`i_*xa^~*(TsFjH)m);_sodT4c*kDYIfVWp
zc#Jpz*MdhPqQLw(v+dL>Ujm~mz)?TKVDh@5Z7PvVyYy7><^{TX7zV-)nw4+Q4_<;VgSp?oy1AJNU5o<6}A3(Q6)*1
zK(J!ypcNlYhC6@0Hr?h8kj1Lh5C@?S&%v)*stzV0r1FSDnULA|bsrgZar31Y~SX8#ZkMr
zm>u4w)?E45;5Kl4%ggo&vVOypY&Y5bcfQILpkdO97jx)!?Uu;^FvATr9{llMZDtbn
zKiCmIdWV`5vTrtsyjXF}D;wXow>A3DWm{Zi_eim^nL71`e9WULmx<>ux3V+FveO%V
zD!*QHMaIRbh=^3EteSvB0YXXBvQhveTTLQD8CtrClwE=nS<;Q;q+`yQ8`u0(JL>q(UIa{w*K6Lcsi=%!31
zc`x$75V{1##1$+Js4p#%7f}oY+4*%Cm=zRmtSLsmVFu}8Z)1U7VT(Z)g>t?wYl70&
zhv|&poedgsSNYm86sqe7eD42b9hgzO$|P1a>#U=MYJS#PEM+hFd(TJp5!eVKrR$pc4r4N9cUl63Va}?{X@=1oBP=aupcl4j*pC>=8e9vpAlm{d7amgLpz(
z>nRE~qgfj-J^$dRDv6;jUP3xza
z5U0^BtgeH`kG5NHNTwN>rExAWUj({jIj(H&?Pi^|1ZoY!&2I^n>kyePcSvZJ`F`ZmhZ8q5v3
zS27ogahS1A{CrWOUb`JpyhvcDpOA*wNf?6BoN~A&*N=;vl@G%$!MPBhm{s%BUR>{Z
zy$O8h+R0NA0rG8oyC6X7xbZz
zjEmNqubX;ys1`m=bjXx%ykQ$4?@sV?y>HTosXEi>tkZ@He0r^bD)4QQp~D_XLY)RR
zdc0z+(Ky@cZ&R+W4#SlzA3R9Xsc3SwT5g;+4oKKOhuX{Bnc-^8I<%+??Vsi}sIwc5
zW%RZ;CtNpc0zO9Plb6C9tJtw5xzzn6vO=l1FGf#pIIZS(HvJ81R3sxt)ttJOBD)ySoJ~N?ik@tiCDQ-}N#3_2$+yw=@x%k{~Ym
zgNLhv67@dRiiM*7TiJ@jF9&+@=o^1u4!Xd&n3T_dQ?j@z0QHl+7I?{&F(u>S!b|};MMXsyn3ytv|IR|1
zD6}^B*`LhKEiFpn0JycW{(j%3!0tAWifkom=zQK_iPY^yd4ZS0rQI_ZstIR8qg)Hl`1$^1PB=?
zw@V_+#i!&@>bgK+m;Fykxd!YaLB~*{W*F~ZQw5m%
zKcna>&tk`$c=*{{2v)|z>CLD+L)QwVdtlOtijKan;C-;#BI@~P;@&nb9o+yha%-hG
z8|bv{;{#-7NaqzT*PT|2Vkb3oQ0l5TIe#epKdZRwz9aPK&zG1t9*4%MLIyy(0W2Oj
zK&9{<=VR?(&9*o|NIkFcxV=9tVgWB^$$f+WvRyZRY%0fdDdAkZCMMKG>Hl
zSrJiGHwP_3CG#Jn(xYOdvQ22ZE2=-7AKCH&{{yQ11SlY2QGr?A3M*&X`spc4OytJ4
z&KZ_YGx0O`VI&yP(KAVdX;CxCeA>wG#}LpC=joN?8s_3Xbfd1609C`YyRo3T9hx#T
zGb3d$=Od<)vCHzgH^fh&y|Z>l{8`Y|H*faL+>!!LXD{pJlm#m&cWG;wEgn`73#7_V
z)$FLoaEoUB;jO*?A4#?^E0hLBo{dS&L(vc=D=QWE@N+f*cln}=VjUa@KO1S-B7on|
zTWO{}3PLmX>0tw_|BiVr@Hye%#K(QLV4P_ymjNvYrs}PIpcw!K5;&qdBR|7@kPW2!`93+}XVv~>8Bzoi4=y-nB{cZJD?8|;1EyLHxR~^!-&YHT#
zfMDUiw{I?RW8P`fgo)>B*1yhQYUHKX>a^I(|GN%rp_;2+)w!YvXo@==rlyk!z3xsL&+xJkI%cv^>l2h51YU1jdvq@j)7dHPRo+SLu
zHMf#)1)}%o2k^O_IdoLz}>B@&p#u1w3f@&5^rZA>4Xq6tzqn<&J%ZVxkh@RV^oKSha+Y$
zt&jpLXEr%AL6e@$_thQTg=L*5m;b98y7Z}t+kis1wU0=rSpV9w0J0yj>dN2=(*jNe
zn5z_Ek#9ishhG4#GP1Or!c$g_wsunIK&SfndK*P@&R>HG&wn?)L&*R7msAYr&Ru0=
zV?#VdFry-(U5V4UEYQN-<8I6k@f60tt`6TKd#?OtVgmLcq{f#^EcCY_b1i(4saq1z
zm^lCVdb){!Ug94ugxSdPigp&~O?a&YUMC4qpxQ+r4iW;-d7wg$>8$mb_+757{-C5;
zZvDw|S$l+Fg}PJu@Co`Ru>a%!`DR
z!kQ#78Gc(RGpnX%hnpg>s44Oy6G9yPAB8+tp!J|9(v=^&61#!#xFI0WmE}(99BRFJ
z-`%A8aqhTx10n4~TQ^>5d&6m&B#cTPwa{;BK9UTOk&=p|FGXDtay9nx@`)$cn)g!W
z>d5fb|7WR>o}derVzH+ArZPkJW!K0ztLP#HbK_nw#D>kvVrSYrvTa8H@Q#`s2Ota*
zaXZS{
zrxg{gD~_r?bxCii7P#?C_fmJLV`|4`Z<3AyMSY*pZA#R297f@DwDQVz!Tj*YA@mQETe%x~@
zmn*+#IYX-TsktQ!Y83I@v8E6hCWq@Pus>+A4V;s^ptx?16d8$lKXy(3jtabtV69ij
zc6^qiT1FN!LiaLtB8m*Q3T>_AYB#4v_eh7Ab+8_fV{acvCGY4DoI~CIh0Dd*wS*tN
zAM#gpWn94(IZZ`I*zxNm5%W*r5k#7tsB!Ag+!O`s=Rasm)E{+lkhqSVaCf}?HR2?5
z9C?Pq4@>;!KNOMNry!qSN?Msqo4>Au%agAlEss2Qh}Uzyi$et}$v>e9boo2QkW=MK
zk0Sk}lJHLJz8S@_%6Ln7k*3W>TLVvOl1kxa(O(=`0@M>v(=}(k&=I
zbc#0CAdmkW<@%cIfplx<1RB*H7Gb*6oCMZg6aABcr?Lx{f6onX{|%l|C`~VX77d16
zTuh9VsArfKchH|+2)8vyp*Yb3W2@`9-*fCMC^;rQs^n`$2|UxT#RWN94-v2Drzb5$
zNy(zavX0VHZ6FP#yowym2v)^o99dNbV2}XFMM4d7-4~a2-{Q(~KV;&L-
zJq2|a{+e)(7oB~^q^EUL-lM9)e1GUZm;3Ct1fG#9@{XxTNJ!3MQlmpyf?-9)B@{!L
z!-_1AWb>q$k`lh|EC@w#m-!z&99SHS5C25uNc+3LkY`W>QLrU?cJ9yy`gHZf*amx#w8{ky%Z}BVV*Cv<4=8CR;?=!EiFeSNr
z>BoSz_I^T&5<@h&7StCbz3tw$1>0wynxK
zp;{@cK=T$%kXRk&p}G96#O?fJE1Lo9)>lgD!zNTsgZg6&U_!XYJ}ooDT>i%WT|FIB
zQGx`|lA2t{USIc{M6ZuU7Nx^(YdLOz6O6{`s>H_2f>s7sKjcw;FPyTnKIgw{jgFqjA;C>Py7ffrQzJXcG3C
z!tkH`t&k2g4Ry{Iy@`A(P|H
zr?0n2geB5>ILdST{7V+q)x+)aY%oiBA8z8KK7Y;xIUy4W-tcc;>$6#)6z&0sN-*Yx
z+nu{w(8$Bb7z~2qx6XL+CBQR?FFBguC>^ZFwhgaOf^VVM1Wib5YI=Z08s-pYaNiPBg4Z}K(VRJx(A!t{Q%Cdhfr2e$Dwq68@05{qA7E!-INpB-?!ZvtbfzLf~bj&+`mNf^c-@BmB
z^t@-_ot~PSA{D`fZ_~j(fpr|wGHqZGu^q^X1q~t_tTNTreDH=)P{6c+ocwt4<~_Jm
z4RF3~5X;9g;C_T?0~*u<>Y+BGynt2Z8yF3rAMR};@>R7G+|i%sP;om!4HkXYbp`Q8
zgDp)jLCSs91dfCSwyc9=oSO|%{XqLv3SY$M=L0?deG>dl?!C3!V@vJ5$Qm!f8ehQh
zzE1?-NOM!ug(^Xq_@L1uj{t7j4#Xak0I+4rqvmwd%9HrofcZP48n9|)Tt+>)0e%Q#
zQL$^uQ-T^;(X*;bZRZCrS7W^OGpjpp;O%v@e;*BIBpCT?KRr0Q2*A`(GS*Gg!Xn@DTar%aACr+N%6clu)V##8w1Od-T|}Oii-cD
zE7TgYruO_=7u74@>%E*|UW)kQJNLK=4BT(~(
zv9Ym;M{DZGN8H!@G>EwY_P_<5#TlKDkZQByYkPLtWVlp&#Zj-i60?R_`W6Y*<^EtH
zQj*FuDtAOw3&b2D_J#r#7<{cDz8{oKAoD%p;#IAF8L9~w7{98LZ_MrccjOz^z6j=M
zYiTi}C$V9Y>rvu*I!9L|T^>Mm_Jf)P(lmpf@mC0#m%r!${0MRWc>N=k&B?6K7_p$Z
zxs?DDlAfIWH@KZ`K*=4(24JDp3E#R!(06FFdNw!zmLEI3V%AqBXH+
zF9A89;L^cw2nRF{4n7whLk04l8&t6-jaKe9c;}neZh*Nj8K(CK;*9OKeQjM&{*kAK&X+H
zlR6YA^*ZfSe`1=2+P^uaH+=?msr~)h3cUeG>ui4D-7nHNF5a^?{2{60wQdn}
zo6(qEP+ZSR&F9|2Gp&SPv(45Hk%P$$%gCO~5VMJk3vWs4>S;x>JoQq`*Vn+jH`M42
z8qfyVJgWI}QhnbOiu-K9s`FdzO3!LzfK;v=96A)~O@5bST088&5BjTyvva}a?7LnC
z>vJm!V{;)%0nXk@5WLREJm`19(LpTFhtm(@y8o>G?q5w+UAcAipiRvbigQee4K8E(YgKxnmw{!f^D0g|0SO8@^us?y{v+
z19SH>?%po1bVucQ
z+_XNeEagZ=;SASdv)1JmlztAg^DWS4^k99EGJ>d5-F_`7t{lBJ6
zkt7clHR3P=n`2)Xdzz74El`axi%$Myib7Rf%x?Pp6Dn9X7+0QywIci*xcCqwCTPC8
zpb9fwxbO|kpU<~RgyC|hBuJj?Dsj`ZnA0(k?rdcTw%7obrP%pI?6F-{*`bZ%WdkTxarzsAMm1VY~szb!JB2J4Ry8RBBY8wqzaOL
zh;&))LDYoa1159rG(`qc(7us6!CyX_0w1ZY)_x1MRfL$JMtoVTR2{uYAlgDbDNK-f
zaSwrRoMzqF6KN3^xJ{sOTz
z9n@Gou=Hqw;{XZ{G$y!ZUbuwf;r@0^etv$n5;ZEhfxRGAc?JZk-Tk{(me!bc9e#od
z-Kntnc-}^#X<+eySVYxh|N5`9y;BVe5XS$b$Y$ay90(C;6aqgiIJ+lFE&`YU@)4^R
zFA8Nn2Pa@r7tA2*5v})4P*7EMWU%*gVT%ZGAhkQcgGmCAt)U?Q-F^&a9Y^e;2OzI+
zc_!70;j>){*{@$86W61H)@`A929=?priMnJM1fLx%p>`VKG+37e$NgJL?O0Su(tL<
zL0%pKd&hn7$^vqkktTr~DctZd`q;5dJAB?SILqrS
z-kxR4gF1Z(XLXQ%@z1MQ;MguGBAZj7GM}7S4t(o_5aV#j0t4QqI7!!ZR=*7mNDA~~
zZ(1;%VB%DOCs7WPXeyX%Mp>uP#Njlsf!TqP4z7o;lVt)w2x1!qPs;$k+U>9K%cZKN
zJj4yLMvRG%&%W$faTh!Scp&i*BTLovGZ>MtL7$%Z4lYDN#VD>jG
zcg#inbtB`z2$_KQ6v1_MdyBjTcf84a$Ogo%vElP);w}pmKzIh>8~~G)z(9cqPv06b
z3Cxj*%@OLn)jrThi_0o2e&`MRiO};IufKn
ztu~NGllsA|b>aMZ&E#?Lu#PlVaR7g;3SUvKmav-vCe8wTe+d-o?|0`s>Qj+rfa4s3
z)1`vY(!w=<2yWr$lMQbGB1IvGen^CB>Dyfz0f?0gJosy+UOqlZvT^3HqEHuTUb6go
z0xJV64BQ8DkfT5b4lj)#0@6eX^zf2r-K++=x(0lltddpH+%d87JAbRYdi~0{dcWYzDQ^wQP`&@mpeE(NO>p#ju2q!
zipR&R+_S;1m~z>vrf}IGH1UbQ*p~xo?7;p8$0$>{5a`68Qv98ql?JI`Mc$!rNhI7D4nFXlD
zT41{u0i^0{egM{!NI2_bmtUI>3qc;PQ!O0BWoI`bQ^BRbGnWMfgwoYVLJi1+wqysT
z2prS8;9cosV;2xmg>fSea24E$(Qp5*M44<@AiPfvBrWQ^a2OBXhf2a$NV-h_3x-Xk
zc%x%nG!XAP{0K*nU=jl`*aG=KaTS17K3K&~G{4VW>YRoCU)~)h?vsniqYxT@!8oD^
z4TE6vu&~5_sFwiain^oF+yDAs5+J(v|FW~4|6h`t2gx*pUG7F+2_Tij9|d{U2YL6N
H{PVv7*kZu$dq-*F_F-WDmJEc3+0|-bn)KJo0
zLk@M{?fJ!B_n&+Jyz8#ZIxal}?AiPK-gw^UdA|9qDF1|vgq8$_LXk;7l~6*VPFtc-
z1V(4iz$=33BZ=_WBebLjTG`47?fBC6HA?O!+S=R-ZEj+4&Ed7Jor#s@eXfUG4{l$3
zgGO81@pE%q{I4J2va&ViW@Nr;4;MLS{Z!Kqg`&QM{2?$-=z+IVBb1hStl|{AGV17d
z8FPHHv71iOLs}c5*m(AA&vQl5f8Smf(2-m=IAF6?^>y0zefxuwRO6p--4fpjdLH{;
zmgIjr|0N!^Pi_1{i?#EV41#e@nNKxqYDcyInJ_97
z%;|_jj}f!02ZctEyQ3({k4{n24?IB$5`8B-Z`k=Ow5R8lh^u^S>%FSjwrC+{)v|IH
z0SlbVsrgA1y
z3$_Y7yFZYf-az|&bCXA1#h?)DoPpXc%t&k7S&|wWu=wKMEZ@>3BBFA;Qmr~LZP=b^
zD4)UvO@KmieS4Zsdjk{jEmJL0lAw+%MON(5H|&ZG|1EmyvZbfKx^uSv`DcH1c8)(O
z72s2@bv?*zDpha6b`H!3pS7G|DARR)bzpnpB^i5e$+PM*#^Q_pD3szUYOz=~p4!Xw
z5?*$Xz74J5aqo3KnJ@J>*zN5fWOm36%r9>IoMxnYr9pq1kJYhE#Ic2chm1`CJFkQi
zPklXHY91t~=`5vw3gu3AQ8Bi3;|pABqn{$4t?0GT=pAGKmw(7|
zWRi3)4ksqxEFnN8Zf%@E&Z*c!ZY61RS5`?4JSI*vb}>kQ>^S;~7}6skfjUT)ZJ&L)
zx@MEX?T1NBX9-)8O5WtGFPG<#$sD=}Ll_);i!UtuL(Z7oK&IvR1rjqQZIA2To0}UB
zR$nQ9MWt*+53D}Vuqd~R&Yl?FB2W?BB8~JOHBy#$`OxuiT+g31K6cA)m$*l6!wYp;
zQA$n!ekQR{>E)T5#b=JtyYIN0jC0FlZwrrQcbA4OZZ9R$>T0!!+|UrV)VaV=(GVYV
z%S^(%6F%sU6la!f{Tiqd;cR*i%8kmWCOZ|Z0gmn=vx`{
zU2jX}cZ0OIa`N3=ykc@BOa^Un=;`JFd29{gVV~U&tLoZd4_tsq)6IQanORAg53PDc
zSCy@&RMZpiEZL?O*Q0rh@Tt-+gX-c~3ak>=f@ibi3YRf#mG*8F{9&b4~1`){>z#n=Xg&xMd89c)1_RqxkZ
z7OML(jKQ(Boigb`;9*6}fnl90&eaX>guljR&!W1Ue!(&y3Sg&l!KeC&xj;(WymQKrTpx@r>}U;?
z8*$M&oi30c$drHIXoA(`J7F*gq^lSkvg;@(CT_WOadk`!ML3;G*+b0a!)HIPp#kkv
zbtd}xCfNm*)hPc{7DHJ+CviT`uc&Q>E{~-K#&CF}J@`rEI0h5gQ(L`@G-8SG?e}G+
zeUqV*CQ_Gblk5D_ZW=x)d4{!y!FfW)WjxXsD~?_vB$jQcd+LUlob+
z`u-#ZTc&N{f#%z{)7lbTA+Ef#$4QNh&6H5$T!;u;aK2A(X2`{)U%V;BdjI|j_g-k|
z<+Rk)H)~_HTBW98WA3}Ua*=l)cM9#sl9F=8t&Uck!MZH==f$Yy>zJS)S>krER%VBL
z8!~cooLy9mj0JmpPIAT~e`t8v?cAiO6?5X4$HMJzVJMqF3kLC$Q
zA8(O{5_2=o4FvA|$mb!GV6;yD!Wf$NCBe*jZy_V0uiQ$1$lND)>HOtJtgN2oXyjCr
zzSL>HJ0l~Nc06u-*3wE667LzDr!S7W;o7q`i_;H}j_eQjY%GRLr2VM5`xQivURjq?
zs_W^+uU5`6xF2l~1gC!a@{iC~mw44i6CEb;F(DBV5u32Eo_4t<=5QZZ`r`YkJgrh0
zk&{E*jOR-wsV#y|QAQL=vVLN63V9@UZ)56p@sLWI3?;JJg@uJAXCKx-m3VPCg#dLQ
z;_8o{(;p&fr8jRfd9B8B&ya0$pU_ZJrg0S1D3J^ACCVoV=RstY_hD*XcX5V~uP+>S
zF8q*`p6iI?@jP~ueDY)td+WvxF}WCCjIQHonr^kj!gpfE2gxcy;*9I(w+Iri;fdJ!
z@m`e{*fP0b^jw@{wsB|t8Fj)?258mx6Ac9LjQYW;kKdi4boz-`P|Zn9Iy-$3CP9#^i+
z%{?U{CB0wvPolrZg{vQYgHBwd8YU(tWIc}Fkqfi>H6~MWYCW_#*=*x}U8wP@P@}KT
z-8JLjSq!fnm7k=ru&|7Rf}^>9rES_&DRt-gU8CPLnzyNt1r9DT#jrPfP}h;fh;BD{
z6T0jS8QWu2)!76EbqELuvegUX^Q%|+ksEFfr03W?DAmeyIXT+3Z|50cg{<=S_BI_V
zN^1(B%dnekwMwqz8*}=(;j}s3!dzF{H!z?MX%{%h8zZ;WpVyoDJf$y5oLEv)vIV~L
z^5sj@(W+9Zz^hplx8yH7Kg+>n2Z_5nvZiF>nF3-Q8XF=L6jX!>03^dHt8%t@6bf6UkM!gi)Q6
z^`eqb81H2G73g!(2soJ6ErtGIxp(HbuYl!?%uk=rHc!p8MYNfgwjNvxSE)wc$Q_Sf
z87`I0kdIR;HN|X}BC8tMtX*dQTuUojOk6x}wYSVd+volJ&{-^N_^9Oo1S5~b^57}Y
zy$g)Oxj#RWn>G4U;-`W{wuX!o9tlZFNqunHS=M}UX)sQ}(R6FRTdT&YP%Td@O(u*)
zzcll~y?ghzj&{dogBLM58FZEvNqx=utzHEl^TBVYu)RG!B^Fp-B+eM!*1cyt6AG5{
ztMeN?AXb&3IFvf947*|N?Cg+&S$~X|MarRpLGqaMT->RG@e-2|{O(%8RCCZAHa;>k
zGfg(abfG5=KOLg5wNgILE;u42JJ3r!PpH(mO1S=Pi&O=@MSxoUm!9vAN(hUbO0|Q9
zbkEKT*8X6ne5riggJWZf`*^CCN-378sc1uXdmyC}0
zVYuA#aH(0H0ykG!eSQ6z^E8P_6e01Z?|GC4X@0anmw=ymM~)<1Ny!7Us}B`36cV`B
z@qFWMy*AJ9K?Tdxuh(l1VpO-aE!tTf&3R-u`)0l?DO;y_c`qjC6w)X6I#ci59=hr{q
z=Z_1``kgD7(zfZeMSxrEN%Na>nSM*H?{;_Gz7>+=eC|IL=n)%2aY#(l#6MnW
z86Veo9Ct4l-k+x6)UVTl$1OHYfRfsKS0ydgO6%zGaC&ZTu7cwS7OVaE@nfIWdWf`I
zXsW>-CofO_d}Q1_`g5L~T>ae{vIj33P%|@>9@i=1+RU=y(>eE{Lg3~))nj-qO56_|
zsQn6WS`Z#1Ixyyrp1Zx?~omN
z;IcLEcEG7uqY;d5<1C;5NiOnxfQo@3KQfYrjOI?mLgmK`^x4J6o!PL|T6J#DbqS9(
zG$N&f81f+O7nTeXN
zqKc==@1J*s&^P<@!zWw6zRpk^Qp@9T;|a1#RSW4cDy62Xqn;;6{4QH>ya)81C>%Gw
zeDOR+2|Q45o%=zF(}sc2;rdUg`>*Sqh40fv1G*W2RodFxIL1ebsMqYEZ&bMH`IClo>9R{NcBgz??8{PKoZ4FG&FCs)k&AxF
zkic)B-8X0bF*oIu}2tHqlq
z0M8(TD_1L~0^M-#q3BP)()jIW{S9w#s;cX%rW<0Q0Cbi4so$;mww#nBAtlAnkddAq
zzMw&T;i@7Odi(W>?*_Z}4*7+K&885JGSbrBU#^R9{kj>HY1kYH%NSDSu#(NCTlH&T
zpuAyeXL;zRw9M0|tQ)Oz=gzIxzepp$93X7Hzf0hB0q;|&)LXp$Tbpisq5MGC!#oF*
z@7y+7@?!s`5MOFqlkl(>!Ew#&y<2z`%5gG~E>aDaAF1iJfYydzVDRRwAvm(3fU&W$wcOPl`StZC{lSTH9KSu%
z35C`h2$AbUR}JUXPKVXVh9%Bc%Zo-pNNnt3!o%>{x9kD})hgo|9Nom5kOnovC|t5W
z&0I6j6HL*`q3t;fE~Db1?b!?gG$zribR%eQQeW|V7cr`W;yan3SQxwNR>H|)^TuI9
z&ZB$OGuiM8&<_IdkDL_t61?1)E9!hOb@_ma>g}V(@Fi|Ve(_Q3i%u&nh
z38WWbl2%TC)?K|;mjh!1C0N7u1CQ
zTFtJWp9}3SfI&#ham?AcZa`=6au&eIBJ2wD@jVK>m(72GeYbdM*<~j$plYL#nV(^8
zSb)^8XihPjf2>@?s&nv9nVriiWbqAQZuzey@sC$=VLI7<^(UTs
z4|SG*G|Jkzxw)aYG3ra7Z^d`PEMUJpcz4wLCs}anK5lLa1`fw%*Nr%KX8K(V0Pu!g
z#9nXbsaL}yjC%=F2Hs50dD7Z?#I)znjg6&xDjWy!t)6`RuhvZ5<(RsGLMgCa3GLmvbuF3)!A
znUPUC#NV9L7{s*O3V?Zs^V^Yv&P7T}N|#~s0Jt}UUHd`TcaDMu=DYjjWwMl%l+4on
zHj@%x4}^}kdW$q}x3;%q+;>OSrx~|N+a5*BLGycH@b6ja9;mu~)s9xoEG$&}(J-DXq(m@!@NMU5gOL&)-#1=agk?^AQ$w9%^d@Q=VqA
z6yU)=Qtp~Wn70-N3k{JP30wk%sQyBrz^bAj73Wn)^wYU_AG*2}rF%YIy!pU+^Dq27
zckUb>8Fllt`8ai}R5-OtSW7;@P&JRNbSRrLJSt0^u)7vCWjwA^WE-Hzd{#z$FcT(M
zQyHqBeNBf!;XHgt9a0AwFUu^ji!%}W{j=>cVSu|KrD1wex0~z0tpE85x5o(^?KrdU
zS$GYPa!*W#s?3X*+{ZhQ-mTSb`t$tjO9B}Ud&jzhU*Ej$(c@0!Msh`Vm?IWwB1n<6
zeqkX(!+!0W=<)G!Yez>vAjCM29_vEDrKZ43$YYV2trowS6FAqQ)?zY{9|tuD4Ks+e
zQn}^m^o(lN;pUj@8ivlQCJ71xGKag&Cw;sMAlu&Vyz1A~0X}szHb~j=7y82J;pD|$jbJ{Sk+}A@Um+_2R1Y?El*rroX2AL
z!6@{9q{~V9)BM>BU3?3{*9xdqz;PArB3M0&485}lo(ISP7cJsh2fP8!!V2a|WIBR}
zL2x3ih(}FR5TpLQ&Eer8iG>|AUesgD+joF?VrZUv=GMxyzz+eM+J}K>ezg~y%**DK
z+U&G%Zs!dq5cWbnCxmevI_phlCZ;zn!OU8!w|a-oxcq
z1yHHA2XyS@lA2)H2U7SSk@zrhdx@u7YI6USeYD*Sdg
z^odZ2kL6`^pN}6oSXiW>&Gi81p!y=R1Lkqn40(E_>1qMk>*?*Kq^HlzDX7y565Rac
zvb~sg*Rbib%Q2*z>Hg*nQY8UQCMsvhFDwrglaP_A78|uA>WS}$m
zz|fIIL`wChEu2&4*)y0j)L*?yP1(`5tV|nG)5JsL|1T(oAt;?zR|5DQ@Uhqq$E>my
z=^na-!~6(F_EFuK2nsxEy}UDsq37F5470SX09w5~lfAL7$*P+#IMvk*)7z}RVfFip
zUtHFoKL-7b5v@DNTW`sD%zo`2Nw0FBElLPfAA6_p&;l35D;b{Mcp@g*5~p$WG5kD(
z`ip8Ho5Ta%4Mim%+C9+24GS%ntnA1sQ|iPBTR$qsuJA`h-4fB~-rskj(+xD~;{I(m
zGaFL1Z?}i`UZEfN
zkhtqeS(CG+WaF++ZR7Lp
z$6ogRnpFMSY9V{{mfEgP>qxDUj1WtCtC`%Z1Z3@az698AwhvLOQk(dpBf9!GfE
zJ1@Kp&*@2LYn5i-rt*XTVE%pcq7jAC5uOaWw~gN#$Lys)+BCyuy)D?+Ikpg1o^iO}
zxAZ5W#Jch2%*>-#e)wfOK{k$mQ*pd5$N
zRu`ui%0hPfvP64!(x>enqL=+WH0=83@5z|YPQG)Q%F3a<^zXQ2yLc*KHp(9ZQ4Ic=&}_!D;04H6~XyI
zrV;@e^hp6hK|#m}h{XB9TKxdj<_Eyq!81QH`s%qq6G30Vxnf&`LW$yEwm;u?ca#1s
z9^;ocW-Buwy#B7IV>(p)wP?%hIgRa&x9(CAHZ5ZZF6EKD6sT76?*V?x*m8vv{6IaE
zw4R<`w2*5V6x5W~Fr0$RWm?*7?)pO|C|0d4`v<4)^78h_&74A64-J+HJbxFj&6QKS
zWp(vrFd~qloO*RxNQm=i9cWYdRW|hRJPd{WRHy^q;F40JB4(yDO>yC?PnY~{%K;W!`Ej|OeFKBEuSexmX+r%9qI&)
zOXrb_upYWr!FCfG#9IBy*v29BZr1K4uJ?C?ar+iPmmy>UD?ko)efLtM_UJUA3n20q
zD(B*RjT79{|NQ)zW53i7Q=@8BJ-KwzkhzQu`vK9r@uB-9=%P{6Qg)u}Nxz$$({M8K
zYEQ3Qw6wGoITt2JaipcCQ4NQSGTxDpQnF35XpwGdB}pWAN*2tKW9%GX+MKEru#1rr
z>H9I$%tMFwshpl3E}2s_{d%BrTj`aC=+}co<xyq6Y2gycccMJ`jlT}!&+KA6jHp|&=`lUIXOPycUX=T6BFA4_5f))z}<-N
z?M(%hy6#$9j8r^_uy8ov%bt^($^>Fc+V5uPZ%6Jct
z<5)5*fU@uYbLntWtc7PcC;5cBJFQ`-rbaMg>#k1K(>2jr%F$*M?U*u)&{t{XPD}~^
zWDQ9~Fz(cv*lmxs4OAUg7y;XGWvx{S=jb=WeDAjCxtvulT-J^o-$d|E&?;~Bpmc~r
zb4KN%)re<`OO~&~cY&b<54=g9^TYh#&2#unw^}OuTGPGD{!Q_o$3%HflYdLFn7dn8
z*Bozoyq(F%)kP}R!!@FM$?=M$=z1R0J`EFQnq*9c?U#PNsl&BYCLYVHDd$~}r~U<4
z%Fkc*o9CFqZX~oDf`UGX?!gb4n_kI6A+6bt*NQm0@_tJxbEcG
z6(T{`b`*tj60|bjb&TGt5Pg1|;n+D_{rk%k6lwsXGX>#k7@kN-gwFH8bkPfh1hX`-
z?dp2Yuj_X18+RbRH8^#tpnh!OF7Ur>z|xP5=zt_s_8^TAH8He2Q;O!3eM=beJ1j(w
z0JV7?t~oMNZxzt`i*;xv)T(wneEDRzIq!Lj6cNk)vD$+*N=_1O3-){WdTaz18xIIk
ziZ4S46d698{eHIWY6=G{odCyAl&JH@y6e3F>MK_koN6EhfNgblb)~L40fvwVsO@Gu
zMGNtvjuet*_e)zO*{Y2LT5TO2Cnx{ZP-l=0@eRF*>f}AgIsCE#%y(>Kj;fI9HjZAK6J6aI?hT
z@ehaF@Cv&_@I>%anKp+O8?OFzNy>g4|Qx1byh+4&s1
z7QDG}Gv(E}Y@KC~omd^p(qqk6uR;%hlE^3OG0?E9B4g6;^4ML&AffaXZ226v*|gLY
z_@b)WXtU7JP$tHNdfYwZ?_|)4q7JMAzJlbyWo)^oseoY@eM4wx+AG*Pe@P}!&Tdtn
zz-v>?dg>JD3yy-q_KTq)e&#R;xxCq1|AW9C&?j>c5*V1A=!Z52g^nXC`G=19h$zjy
zm2v9XzWuqYk5DL;%c?gxOeV;-bscJ+5cAm$6?)0ebbpt--d3xqn;O
zZ5+erTYwz5j@$jhlpypf0=E?Xa2|B3XxI|8#6HeX>%$p!)01!TPv
zhZQYoEo|)UG6A$aJs_FnyROx$U%Ys+QU2L&X%JLH8V$YzNuRG<-IppIa_WGInfVdR
zBa4221!4jerFx!e*!Ispy~Uk^wJ?`-GzV^LN$rX9$>?(`zs5!k0W&q0No?EnG~4QX
zvsrF*M2EVnYS^zIbFWV;+Ki}I*RAHbn>hZM-J(;G$LI)GOf~tybEX2vI}bElYfDS_
zXNp@TF59L7GABf&e
z6RIyx>=JH|PA_4qmGe#=2nuFq?Qk<>CJ4{x#T4YVt+PhTs5p6@skHvFvbi&{A~!v^
z^yo1K%IU^iIzE2%{1+Isn5F+43?C;QoH=s_;af?_$p-*8VN#_je0+Qmz=bGSHMO5M1FLu?H*VhK0ha6a>#!9t
zOigI!n_tR0sU0kQ*8B~d_HSseoBmGEoap9G*_&CjQI%qL@zec}#x+
zpRm0&FmN+SFgZ5%DqMWzgMj741|s~!>q`jl14{R0(5--x)CV>L$<@m=G;h$K8<+tQ
zd_iPaWS9Z@q6@Mr43xc2k&}`69u&%QtE(YhM!K`#Z|&F!%@&;6skf5tF}g&fdxrfJ
z;IT}gevn}YI2~Zal<5I(LwczKHw|QkKi5TT(zkSx{grle(`#$oj0tZ~p5Zn(LqkGR
z8X82mSFq{;#(xcr73hoDgJ6UpX4t8v{9uBJED)QhxO7;hU6u=*FJHQpoTZ$x2>8~J
z=MgINg_N*$TFq^$+Vu{jpQh`uGEIB=Rn=BEf0~Lm;wJsL5lscLq(C)Z8Wgr1c{tQ&
zyF@ebl7^i~xp2rFzy6aPxn0C_0VEQ_IJ`ecQ39d{y|9}GQcRID4%D4=Ft8xgPVSbL
z`N6@#eZZqY=$pXMA?3~?8YQ?hXt*CQ0wim2Dx=5V_fNJc)EU>~W3_1E^x{*8wWA)Z
z-(HBgAIK|+(N%=C8~q&zU=E%kwcvUY$gIKy7LqU?v;O%obv;D;&6D(_S^z(TB9v|(
zxam?0tQ!2Y7uab`v9SX1Nh-miQBhfduq1)}4F|P1IXU?0kB;;q$f!0t*|)`
z<45!f5Y%QZPkE6ndmqb{9K~p3{Es}hPp%Zo$Of};4t~}gZ=$1u$;(aXFK9@u8V(|hoo{aZ)eJLY^lU}r@zH^5
zy@z`<4u-{FM#Wh`TsKh?q3#etp%D_8CfQzlu5fZ-8XWo=sK}-bp-8#$Cy>smCdRT#8lbTX7;_NM3kdY8fRdG=Hgu;Eq&duY
zT}LcLi{SI{INDZ+?kejzY9Enbvnd04jf#Bsf|>DXshMZk2QD5Aj2?YzEd5&tn`Tr)H4^ket0p!scq;j}vrO
zLYxNxjwz|AjL@IGw1L>4sy$e(-rU*g2YeS67M3O-$8Q2+C)9}u}_ZUZlNOa_a~c9)Mpb02)j1
z1LKaXhJH}P?`S~MB6gRQHq`mXt9*#h8--$#v?l!fj~7a(|LEse9=2pKW|X$s;K&&azWlWNuc*tEa7yf827T=vTR|7j|*o1{?MFeJI5M
zXiSz7TIQb)2D9HLnA}ow?tE>Zsn
zzoX!D0TCUQX<@ISJ~BtoieHLse{Zl&QXiv{_hi%peZ{M-??Qw@_CF)=jRtYp+u6fb
z=CnI@v(Jz!2LwC9)>^zgxy8jL54V}%zN1-r6)a7c`xZZA&ste)X3}%nTTU=@bHlr{
zgvQ0?QR_K9hGAF{5o*Ew1tLb@d=aXAu*SKV!Sm3j^i8)IknL(n*u3ew2B^PtzQ?Pr
z36#kP=y^r(8YzO9-TQ>meY(u<1o{K*eItKt2k}F@%3XpVbWZ#?#pymjvP6Y8KsDxZ
zT-6cU?o&1$sVEdaSiV2%G!>v#XyC)|xN44e7Ong%5&=*#nP~6^=oSX~lXh~n=84$7
zc+CeBVTPpDy)m%?&P6tm%E9%Z4&d5$snT=r4>5E=*Z1H)lAI^Zi?5KozMXA)qRxRf
z*k&x6(`?&z^WuNHxv;Z}|3Utve5OUu1=W3o&Bi3Pr+_pH|0W=d;|8?
zGb*Pee?l4B1`5IqtagZv5-1Soj02EHR#FN^pfgCfrqH~hqB+E-K+9MJP0ZU_01UNU3x}iU
zJI>SdtK2vG{Uy~67d*JKBYS+}?fX$!(dGE%V^hIBEglB9m3`chd8<$io3^}OWjr;j
zKXCJP-@!`0U#&kBl<-CKSn?7)c@L;wh|B8BmoIKuY^m8T>ESwef$wDOS7A!%%}~gJ
z4v+L%19(8hu!r<+kT4M84`?bi;OlTJyP&u&Aj(!1C4gvfjt`jkd)&c=W0{k;b?o85r034smTU%(b&L;gH
zZx$yPN(I#R1|;R^M|MV0K_7kJ2~TJtwsId8-sMBDA!Pr0T0(BA!rGp~a+?d-Ouny{
zJ$0TB93=?41B=uBs>==kAZE5@hiZtN6d1^!23yu2Iq2FnQUDIDCx{gn&}pcl
z>&fvE;;1wMP=>r~Qzo8R_}aiqD?;Ben>brZb*3i#3Wdq9){X*@+ULf+{P|O>*kgsjDlZl10Xy3_^g*
z5C!YQ`6~wKM_yQ8&)Fvowp1kXAOe4@O&X(IWK#lx6-p7-A3&8*wi-
z#xG6s9
z2AUzR6rfyKvPr=kc~O2yB1c(MLP3@E4G6%1kQW*sA8++`G61H+Ved9wHMMX=%L81DpaIjd
zn#yiwf)5Cuf~_m@JUKpP;kvtOFK9XVEeT=+;oKu5BV~m?s+8pavK|b7p@>rCgxrr-3D5wX(7T#4J1RagYao`pLmMU;vT551a!!E26x-
zyeW`g`-RC%NXOLFRD({tm*b?iwzdzzqIT*4O0y!&K#Bl(4Y8Are)dpN4C)hF!#WFI
zO)blDPn<`L05;8JUN=gu#`OS#_Cefcf`AN-=P48rMexT9AMY8av=O$B7IzBgtkoUJ
z1J{apq=2RmD~MBlsCW^kbv2Qrtq>%vpx>x~zF^qw0p!v!
zEF#7}u>15w4g0J7GYac~2VN&>ZXV41KYsq?dG+fbq5bI)z~4wpd-F8}BUPh4PCy-u
z5=aq2uTMNiYABFiw|89Oh(Q3;Ld&fLl>)umX{gcys}(7sC&r4;|A+m@%K&kdxqyts
z1ML`W37Ih4gJajG4(s6Eh5;`uvo!b!dXgpmNJ=P15^o?K&f|FBSP1v~zROIQx(bN<
z$TQwjh)36$grx$T#16VHQo_NKz$^`;OFtRkNFr$FmA~x3=w-Sw*@*bAw|iu{5#Kd9
zCWF8j`PFKMK^jTaF^>Za#DJ|Jw2kmG_snev
z_^0?2#B{!2q4@#U}
zF{N@mjl2OWiirgTl&4Ur(5ncZU3Zes(!`tp@!-T-i-E_K{W^0_m-~s(Yd@wIgRh`9
zD`RJ$R~0J|ZZE3H>giEAh5==xcmsjv4TcuOUlX(R*ZS2(j?$K|E-}~kTShpXgk_I<
zrkT!85}GPRiI{Xkf
zPL9RFbm%X}4EhSDtEFB~^`-CQrbjz_wvV1T1gcU9W0U&d
z)nh)4x6NIJXJ`I9KM4K+KPcwUm?SOFKHtpraO;tq>xHze-Y<9-6r(PB;Fvm>u%cLhD|}u@Dqp)3ZuS~9RM=|mx{dewp7T%A;#%(J
zen_j2?;;~VNxeVkp93MF^6c-$N_Y<$3Sts;vVY&6`pBL)&QPHJt>gAf+v4FIM=J&S>+9o@0Z8Y@e|x!i3-OHPZ+lQ3S8svVL+c$yaeuRI67-Qn@p%xgU{@K
zeR(lSeSnR*);tLg_=-yZT}XY#M9DkX69C$@XC
z0Jo$FKH>arEF@N)g+ed^28ELGdt}+#g19!2143}9VtfaReM>?0(b4Uxq##cA{t$iYtk-`{Ry-@&VKibeDwlW
zAit_k!gvr5qsvK}JncvfIXOW?E)DyP0Z?f6?|I?a3N4(oLWT!Wf%TB_1ON--KL=4p
zCRop7|8=ujLDeF=xG7M-=goPm(>;o@z*tMGm8m9hj#$B%1#LLq!|q+Yi=AO%QhTFP
zj~%)h$Fa5JPd}qUk4n4^8u}aX^n%=f%q+dRx!D5~KMzdXs`)yP#yx=6!NcJX#B>uI
zTi?9i+|*Q6tON1!dbF%}*z@5iyxCAQ>CP41Q;FTm<%@Be0Y+o}3?1Lc+T
z&qzfV&;<5~_p0Z9r(g;a?BzLgn{%$a{O;
z16rvFZ<052-k0<6E1_+HyW5=~w|4ASC@DMMV^J@tPV;fSD93KYWd-8LZJOzsU39@G
zFjZK*Zvh?{WI#bIO>n{mQlq6@D=cTooR^t7?1)0VzS#Kk{Htn=sj8`%SX5Au>|9uE
z?iaCP@XWr(O>%LmgZGTHgwtF2#V7yNX)$bYNN^Y>$oE={QCE-bm!BcReAP52=7#u@
zY)Kv~lzZu5MQ8EsRZV_yj0xFuQ@Qsib
zBJ3?7GU|w0WZ888^iTOg82W9|l8zMaH
zRPIDxAx?W&P`wkGwmzPzmp!Sx&b6b^Q}#yANGB?&bNixK@`L=Vsc{3N^Cfw!!E5My
z)f1(U-P~%DQ&M#GNbN={8veJq{{DZ6>ntpF58*`XMA7Vw_Mxzm(Z)LdPi2iCW4z}=
z^LdA(y9B5-DG&}3kR%xR=@V
zQ7z$<9rMQXWT#OTl#Jw+qOg5a8*nIr^tT*N0csisT&wh*abzL)#Nb;%0<^hZtq(Ix
zjnW5Tllk1R(eudpzu=2xs-19K{RA2Di;8lgge)zj;$=DSg#?`!)!<2z#nW)MJsm@b(sm|C4mhzKpAY({Y-6l@%(M^DSc7dBUcLA*y@tS1-b;=Gi%e
zivDAtJnTAOwQi%rC7n~!CPQ40IvQyQNOvoaBqz`Pw+w3-_I0s&IqoOGVL9)`^fBMyRvQ$
z$AcEC95A3q3miO9l*p`a3nWId3cw{7u;m4Mo=5i0b}*X3ArCVUS&_mU$S9IOKmW!Z
z*UpC=#eg#nOj$tcXDg-oK7UP!dO>RuEqO&(EUR>9V))N~Je{qNY~a+CVRwjQsdY;s
z3Y9NzJtcPYNnju1(|FN<0;C`ZJRlq!s6qxML}En@)?f+6k)=#p0_EAUM5B!X9X?I+
z=N+Fj2UkFn`UcGN5~f+3y58`vFhj3opwo7Y^0(@Kubh6oBr
zt{@4IYTB9fAVs#4=_j7=Zq;eg6OTJ;#`ib3#ubh$rU$fc_e_7KM~}A$v?bj`iE7$3
z2M|jLxAHWSvdIuq2{M1wMw}1hJLwe2XTEpWBqIxw#%*<(mve3~jR+k1e
zyb0p%#MgD*Y1swef7W@)Ku&I&KGn9`_pt!2loRRTOG(%D_*{xIahZI
z)X(2``TkrKh-cizrHTm|{>f+`u%gFdeGwz3JT^JyM9-lFhymo-gGn3;MRWbZpaLi|
zspNCZcdJ4#ZIlSSSG;?g2yeRQmF}{>!veFwV}QYI%tG1#<$XTxEwr+HmHfbuLfKuf
zuqpPNEB_o201fQ!H(C_xxAE_r>Lwk*;xh7Wyj7t$Mm@FtwfKyA*diku4W1vUsrJ=T
zazvi;x@DLd8l&5K$O}Vz$fzavj$gF}3KbDz`rI2o?nm#>mGZPSTdn`!xnVk82>VRb
zS<8{-1Ozj9y<
zX$$+dO|Vk&=q%lIL%_?Smyt`Nl(VTQGa1L%(k5D&^@sNomL1sc
zGINYajOyW|T^zI1c8^C3w}{~fDE
z0wAbz?e9!)u&hb(%@v$dk7Eh4{?)*$154=%O(cj9e)%R)=ADKo+#;8A#_Osi^J1QawSn;j
zCFASrL4fjV{+vafJ;OMjIljxXaAdXf28^d*FIG=C`w=b2gTYS8Fl728
z`#b$lgXlA$K_G2e<;^40dhGp&p
zH_-H2bI%xzjw8s&`82%Fka@+ty`ZG+Ue(82br~*u@1?B)JQ95UKc1|;>4pNhP#c#
zgirlc;*BC>BdW3awXt#W(|p#otzmIi3L-aME#qCy2+mJXQ>nbsHgd~8fK6!-pLspQ
zDzFmU%UjLQXH~9|+8|mUE@n+QKzh!hPKZx^N5r9`oT>qJ{KtowuQ}Yp
z#*i6|cRP$;3ReBkCPJr^YpML82sF1IX(U1>(NJcuIq
z>2g$5QGwoCNqbLe%59(X7UDy0yWjcKnE4YlHr{^u)k@-*e_P*E-`QDnL`AbPBqlDS
zLN_C}3}dDzS9dIInSb^U!_g@t!0%xpaT&#IS7d}|4trNf4^O#0m{?fd+;M2miB?G0e2U`4&UpjFLl^NgsI%TcjZ5kY*!_7&lph>-mJlf#3;Qw6!
zpCqD_Bx&zwV7Bp?5%tc=was;;e#Tysv^*ht+>Mn_Pkc34y?_C1DKzOel-!N|lUz_*
zTeEE_{ql2L##K;fHg>{yaSB&14L^m2egZ7Mv(P>N^)r-Oev9vcWC@j=VyDv8ZNA~>
zQ=fr4Z+*RFp{wKDB^q`gTjTPfR|gXmLmpbDXg05x4)G->2X{S2!OP8Nkgd@d-gU<7
zxtrVc(TNldEpm11n+Gk*MS0k3*ebG~@8VJsgcZ6*fx1tWIy@Vxg-&juP?TyCi7K~#
zB=i5FA|n205M_Ozkx?_>l)0dT{*}pqT6n9slse><;h;i%uthHw8?)+8@tm)|J4waw
zSF!Egh3};KBjVFymj!2xv>Uk%QKFL5OLLqeo{U6i_Op!MohABM7)^oV+bsH}R9SRc
zMKe5_=e5Z|)8&`#jsN@Oy;?p)e!|!Phd%pj;!>nH~0jbf?
zSw3Q5o%VONoGz?Zc*{CDJ0p4}9>jehzWf$sH8;TVM8SO^7yn*Ns9&h1%xq7vej#RJXCXf1C%UcOFO6CM0P-dq
zHrnS>kIWK^*Jjv8jaE}iB63A5E0Sh@$2cQZsqG)YDG55AK5(9pM}dh-NOSVZ^Y-`|
z;a4Es!`j;!2y_DRW^sQ-H~|?5>n}wj`N5IoG#L!Kt=o}l2heaCD%DD`m7hWHMBJ=U
z)F#HNRxM&yqR)r+?I3w1V5herlPglL-enxbh+^bZNUxJ9Hy$Vl5#W}V+N~dAjjI$B
zw3#UO04em$YL!G4QslpSiD=YQ1k?;H+!qSWpqtb~;Iv*}bf?_x$QYW{i?_3rJ*86A
zfXFi;YWc0ulQ&cqHSK_403}l!E!t4;Wn*jW`TlKK+~9e4i@sUQD|5j9qL0E?z=CSM
zW#-e;WkI{I)PU@5{R14RU$q*|RAccok)wzr$B*tTObv-i~BC1Qd`$|
z!SJ%;t1?+dKl5yo*GyulP(M^26S;Pw%`(&cKB898{do2mfU*bEuP|hJnvN^{(oR5x
zOGmxsDm(V<++2^*)=~B1BWUAyd<)k0co%{Y33Lx
zloUl-L?lcK>Q?*A&$@o^HE1z(MhVID)0G5sM;1~4pHEI`F2r`_{e;C{Zox=woxOFh
z#HxF7x2L6W1jtUsWAi|fyLE4Zjs$*;*3gai`W?#Sf(3!aE@~&To#nwdr8Hj>lDYxp
zC#{^E=DVyU?$*KMH@iei1v!gRU(ocjt=Z5}v}|V*mg9VdKw8F6ef|96uzKPfA?8I%
zUASzR*8y4Q#HkcG%-DN)47DO$A~ih*{D2r>chYvq>7xvxaMqQ2m_o%+{#kNGxguH250>lb@A5`d&Y-4EC5Scwy!mL}%g(Oj
z2B&lIFU!-kvZ!ufEkA1gWpBsJZiAf(2zXqQ>$(l{9I
zfQ=zIw+y#|atoJOJK#tV{zP1RGDF?TR{C-(#7ol#378W4YJ&4mu(r|-$i*UAX5s#A
z9+8*8H-VjXW^vM%Ahi%}5r7kDdUipZ-DX~E#;
z4vNEf95JSwbNxKJ!vWm^EU}wRKbD?iT~=SY=$yER;~VhYFYkYz`VJr(0Apm+q2o
NWBUd9%6%f!{sm`4u~`5B

literal 0
HcmV?d00001

diff --git a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
index e99cb3f628d8..9e3dd5cc48eb 100644
--- a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
+++ b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
@@ -47,7 +47,7 @@
 )
 from qiskit.circuit import Parameter, Qubit, Clbit, IfElseOp, SwitchCaseOp
 from qiskit.circuit.library import IQP
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from qiskit.quantum_info import random_clifford
 from qiskit.quantum_info.random import random_unitary
 from qiskit.utils import optionals
@@ -2300,6 +2300,59 @@ def test_no_qreg_names_after_layout(self):
         )
         self.assertGreaterEqual(ratio, self.threshold)
 
+    def test_if_else_standalone_var(self):
+        """Test if/else with standalone Var."""
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        b = qc.add_var("b", False)
+        qc.store(a, 128)
+        with qc.if_test(expr.logic_not(b)):
+            # Mix old-style and new-style.
+            with qc.if_test(expr.equal(b, qc.clbits[0])):
+                qc.cx(0, 1)
+            c = qc.add_var("c", b)
+            with qc.if_test(expr.logic_and(c, expr.equal(a, 128))):
+                qc.h(0)
+        fname = "if_else_standalone_var.png"
+        self.circuit_drawer(qc, output="mpl", filename=fname)
+
+        ratio = VisualTestUtilities._save_diff(
+            self._image_path(fname),
+            self._reference_path(fname),
+            fname,
+            FAILURE_DIFF_DIR,
+            FAILURE_PREFIX,
+        )
+        self.assertGreaterEqual(ratio, self.threshold)
+
+    def test_switch_standalone_var(self):
+        """Test switch with standalone Var."""
+        a = expr.Var.new("a", types.Uint(8))
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        b = qc.add_var("b", expr.lift(5, a.type))
+        with qc.switch(expr.bit_not(a)) as case:
+            with case(0):
+                with qc.switch(b) as case2:
+                    with case2(2):
+                        qc.cx(0, 1)
+                    with case2(case2.DEFAULT):
+                        qc.cx(1, 0)
+            with case(case.DEFAULT):
+                c = qc.add_var("c", expr.equal(a, b))
+                with qc.if_test(c):
+                    qc.h(0)
+        fname = "switch_standalone_var.png"
+        self.circuit_drawer(qc, output="mpl", filename=fname)
+
+        ratio = VisualTestUtilities._save_diff(
+            self._image_path(fname),
+            self._reference_path(fname),
+            fname,
+            FAILURE_DIFF_DIR,
+            FAILURE_PREFIX,
+        )
+        self.assertGreaterEqual(ratio, self.threshold)
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=1)

From 9367f7b6f4bfcd523ad8919cd65e2f6c4cad2be2 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Wed, 1 May 2024 19:20:09 -0400
Subject: [PATCH 052/179] Restrict iteration of commutation passes to just
 qubits (#12318)

The CommutativeCancellation and CommutationAnalysis passes were
previously iterating over the full set of wires in the DAGCircuit.
However the wire list contains classical bits and also classical
variables after #12204 merges. The only thing these passes are concerned
about are computing whether quantum operations commute and therefore
don't need to work with any classical components in the DAG. In general
these exta iterations were just no-ops because there wouldn't be
anything evaluated on the wires, but doing this will avoid any potential
overhead and limiting the search space to just where there is potential
work to do.
---
 qiskit/transpiler/passes/optimization/commutation_analysis.py | 4 ++--
 .../passes/optimization/commutative_cancellation.py           | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/qiskit/transpiler/passes/optimization/commutation_analysis.py b/qiskit/transpiler/passes/optimization/commutation_analysis.py
index 751e3d8d4f5f..eddb659f0a25 100644
--- a/qiskit/transpiler/passes/optimization/commutation_analysis.py
+++ b/qiskit/transpiler/passes/optimization/commutation_analysis.py
@@ -47,7 +47,7 @@ def run(self, dag):
         # self.property_set['commutation_set'][wire][(node, wire)] will give the
         # commutation set that contains node.
 
-        for wire in dag.wires:
+        for wire in dag.qubits:
             self.property_set["commutation_set"][wire] = []
 
         # Add edges to the dictionary for each qubit
@@ -56,7 +56,7 @@ def run(self, dag):
                 self.property_set["commutation_set"][(node, edge_wire)] = -1
 
         # Construct the commutation set
-        for wire in dag.wires:
+        for wire in dag.qubits:
 
             for current_gate in dag.nodes_on_wire(wire):
 
diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py
index b0eb6bd24137..396186fa95cc 100644
--- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py
+++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py
@@ -99,7 +99,7 @@ def run(self, dag):
         #  - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id,
         #    sec_commutation_set_id), the value is the list gates that share the same gate type,
         #    qubits and commutation sets.
-        for wire in dag.wires:
+        for wire in dag.qubits:
             wire_commutation_set = self.property_set["commutation_set"][wire]
 
             for com_set_idx, com_set in enumerate(wire_commutation_set):

From 676a5ed98d84151b7bb5948d431c27202d9eae5f Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 2 May 2024 02:25:25 +0100
Subject: [PATCH 053/179] Support standalone `Var`s in OQ3 exporter (#12308)

* Support standalone `Var`s in OQ3 exporter

This adds the remaining support needed for the OpenQASM 3 exporter to
support standalone variables.  The way the circuit model handles
closures over these variables makes it much easier to support these than
it was to handle free-form control-flow operations.

This PR somewhat refactors small parts of the exporter to better isolate
the "top-level program" statement construction and analysis from the
"build a scoped set of instructions" logic, which makes it rather easier
to handle things like declaring IO variables only in the global scope,
but locally declared variables in _all_ relevant scopes.

* Remove references to QSS

* Clarify comment about forward declarations

* Add test for parameter/gate clash resolution
---
 qiskit/qasm3/exporter.py                      | 261 +++++++++++-------
 ...parameter-gate-clash-34ef7b0383849a78.yaml |   7 +
 test/python/qasm3/test_export.py              | 172 +++++++++++-
 3 files changed, 336 insertions(+), 104 deletions(-)
 create mode 100644 releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml

diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index 329d09830ac5..b85e35e22a50 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -33,6 +33,7 @@
     Qubit,
     Reset,
     Delay,
+    Store,
 )
 from qiskit.circuit.bit import Bit
 from qiskit.circuit.classical import expr, types
@@ -62,7 +63,6 @@
 _RESERVED_KEYWORDS = frozenset(
     {
         "OPENQASM",
-        "U",
         "angle",
         "array",
         "barrier",
@@ -239,6 +239,7 @@ class GlobalNamespace:
 
     def __init__(self, includelist, basis_gates=()):
         self._data = {gate: self.BASIS_GATE for gate in basis_gates}
+        self._data["U"] = self.BASIS_GATE
 
         for includefile in includelist:
             if includefile == "stdgates.inc":
@@ -282,6 +283,10 @@ def __contains__(self, instruction):
             return True
         return False
 
+    def has_symbol(self, name: str) -> bool:
+        """Whether a symbol's name is present in the table."""
+        return name in self._data
+
     def register(self, instruction):
         """Register an instruction in the namespace"""
         # The second part of the condition is a nasty hack to ensure that gates that come with at
@@ -324,7 +329,7 @@ def register(self, instruction):
 class QASM3Builder:
     """QASM3 builder constructs an AST from a QuantumCircuit."""
 
-    builtins = (Barrier, Measure, Reset, Delay, BreakLoopOp, ContinueLoopOp)
+    builtins = (Barrier, Measure, Reset, Delay, BreakLoopOp, ContinueLoopOp, Store)
     loose_bit_prefix = "_bit"
     loose_qubit_prefix = "_qubit"
     gate_parameter_prefix = "_gate_p"
@@ -348,14 +353,12 @@ def __init__(
         self.includeslist = includeslist
         # `_global_io_declarations` and `_global_classical_declarations` are stateful, and any
         # operation that needs a parameter can append to them during the build.  We make all
-        # classical declarations global because the IBM QSS stack (our initial consumer of OQ3
-        # strings) prefers declarations to all be global, and it's valid OQ3, so it's not vendor
+        # classical declarations global because the IBM qe-compiler stack (our initial consumer of
+        # OQ3 strings) prefers declarations to all be global, and it's valid OQ3, so it's not vendor
         # lock-in.  It's possibly slightly memory inefficient, but that's not likely to be a problem
         # in the near term.
         self._global_io_declarations = []
-        self._global_classical_declarations = []
-        self._gate_to_declare = {}
-        self._opaque_to_declare = {}
+        self._global_classical_forward_declarations = []
         # An arbitrary counter to help with generation of unique ids for symbol names when there are
         # clashes (though we generally prefer to keep user names if possible).
         self._counter = itertools.count()
@@ -367,18 +370,15 @@ def __init__(
     def _unique_name(self, prefix: str, scope: _Scope) -> str:
         table = scope.symbol_map
         name = basename = _escape_invalid_identifier(prefix)
-        while name in table or name in _RESERVED_KEYWORDS:
+        while name in table or name in _RESERVED_KEYWORDS or self.global_namespace.has_symbol(name):
             name = f"{basename}__generated{next(self._counter)}"
         return name
 
     def _register_gate(self, gate):
         self.global_namespace.register(gate)
-        self._gate_to_declare[id(gate)] = gate
 
     def _register_opaque(self, instruction):
-        if instruction not in self.global_namespace:
-            self.global_namespace.register(instruction)
-            self._opaque_to_declare[id(instruction)] = instruction
+        self.global_namespace.register(instruction)
 
     def _register_variable(self, variable, scope: _Scope, name=None) -> ast.Identifier:
         """Register a variable in the symbol table for the given scope, returning the name that
@@ -399,6 +399,10 @@ def _register_variable(self, variable, scope: _Scope, name=None) -> ast.Identifi
                 raise QASM3ExporterError(
                     f"tried to reserve '{name}', but it is already used by '{table[name]}'"
                 )
+            if self.global_namespace.has_symbol(name):
+                raise QASM3ExporterError(
+                    f"tried to reserve '{name}', but it is already used by a gate"
+                )
         else:
             name = self._unique_name(variable.name, scope)
         identifier = ast.Identifier(name)
@@ -441,15 +445,66 @@ def build_header(self):
 
     def build_program(self):
         """Builds a Program"""
-        self.hoist_declarations(self.global_scope(assert_=True).circuit.data)
-        return ast.Program(self.build_header(), self.build_global_statements())
+        circuit = self.global_scope(assert_=True).circuit
+        if circuit.num_captured_vars:
+            raise QASM3ExporterError(
+                "cannot export an inner scope with captured variables as a top-level program"
+            )
+        header = self.build_header()
 
-    def hoist_declarations(self, instructions):
-        """Walks the definitions in gates/instructions to make a list of gates to declare."""
+        opaques_to_declare, gates_to_declare = self.hoist_declarations(
+            circuit.data, opaques=[], gates=[]
+        )
+        opaque_definitions = [
+            self.build_opaque_definition(instruction) for instruction in opaques_to_declare
+        ]
+        gate_definitions = [
+            self.build_gate_definition(instruction) for instruction in gates_to_declare
+        ]
+
+        # Early IBM runtime paramterisation uses unbound `Parameter` instances as `input` variables,
+        # not the explicit realtime `Var` variables, so we need this explicit scan.
+        self.hoist_global_parameter_declarations()
+        # Qiskit's clbits and classical registers need to get mapped to implicit OQ3 variables, but
+        # only if they're in the top-level circuit.  The QuantumCircuit data model is that inner
+        # clbits are bound to outer bits, and inner registers must be closing over outer ones.
+        self.hoist_classical_register_declarations()
+        # We hoist registers before new-style vars because registers are an older part of the data
+        # model (and used implicitly in PrimitivesV2 outputs) so they get the first go at reserving
+        # names in the symbol table.
+        self.hoist_classical_io_var_declarations()
+
+        # Similarly, QuantumCircuit qubits/registers are only new variables in the global scope.
+        quantum_declarations = self.build_quantum_declarations()
+        # This call has side-effects - it can populate `self._global_io_declarations` and
+        # `self._global_classical_declarations` as a courtesy to the qe-compiler that prefers our
+        # hacky temporary `switch` target variables to be globally defined.
+        main_statements = self.build_current_scope()
+
+        statements = [
+            statement
+            for source in (
+                # In older versions of the reference OQ3 grammar, IO declarations had to come before
+                # anything else, so we keep doing that as a courtesy.
+                self._global_io_declarations,
+                opaque_definitions,
+                gate_definitions,
+                self._global_classical_forward_declarations,
+                quantum_declarations,
+                main_statements,
+            )
+            for statement in source
+        ]
+        return ast.Program(header, statements)
+
+    def hoist_declarations(self, instructions, *, opaques, gates):
+        """Walks the definitions in gates/instructions to make a list of gates to declare.
+
+        Mutates ``opaques`` and ``gates`` in-place if given, and returns them."""
         for instruction in instructions:
             if isinstance(instruction.operation, ControlFlowOp):
                 for block in instruction.operation.blocks:
-                    self.hoist_declarations(block.data)
+                    self.hoist_declarations(block.data, opaques=opaques, gates=gates)
                 continue
             if instruction.operation in self.global_namespace or isinstance(
                 instruction.operation, self.builtins
@@ -461,15 +516,20 @@ def hoist_declarations(self, instructions):
                 # tree, but isn't an OQ3 built-in.  We use `isinstance` because we haven't fully
                 # fixed what the name/class distinction is (there's a test from the original OQ3
                 # exporter that tries a naming collision with 'cx').
-                if instruction.operation not in self.global_namespace:
-                    self._register_gate(instruction.operation)
-            if instruction.operation.definition is None:
+                self._register_gate(instruction.operation)
+                gates.append(instruction.operation)
+            elif instruction.operation.definition is None:
                 self._register_opaque(instruction.operation)
+                opaques.append(instruction.operation)
             elif not isinstance(instruction.operation, Gate):
                 raise QASM3ExporterError("Exporting non-unitary instructions is not yet supported.")
             else:
-                self.hoist_declarations(instruction.operation.definition.data)
+                self.hoist_declarations(
+                    instruction.operation.definition.data, opaques=opaques, gates=gates
+                )
                 self._register_gate(instruction.operation)
+                gates.append(instruction.operation)
+        return opaques, gates
 
     def global_scope(self, assert_=False):
         """Return the global circuit scope that is used as the basis of the full program.  If
@@ -540,40 +600,6 @@ def build_includes(self):
         """Builds a list of included files."""
         return [ast.Include(filename) for filename in self.includeslist]
 
-    def build_global_statements(self) -> List[ast.Statement]:
-        """Get a list of the statements that form the global scope of the program."""
-        definitions = self.build_definitions()
-        # These two "declarations" functions populate stateful variables, since the calls to
-        # `build_quantum_instructions` might also append to those declarations.
-        self.build_parameter_declarations()
-        self.build_classical_declarations()
-        context = self.global_scope(assert_=True).circuit
-        quantum_declarations = self.build_quantum_declarations()
-        quantum_instructions = self.build_quantum_instructions(context.data)
-
-        return [
-            statement
-            for source in (
-                # In older versions of the reference OQ3 grammar, IO declarations had to come before
-                # anything else, so we keep doing that as a courtesy.
-                self._global_io_declarations,
-                definitions,
-                self._global_classical_declarations,
-                quantum_declarations,
-                quantum_instructions,
-            )
-            for statement in source
-        ]
-
-    def build_definitions(self):
-        """Builds all the definition."""
-        ret = []
-        for instruction in self._opaque_to_declare.values():
-            ret.append(self.build_opaque_definition(instruction))
-        for instruction in self._gate_to_declare.values():
-            ret.append(self.build_gate_definition(instruction))
-        return ret
-
     def build_opaque_definition(self, instruction):
         """Builds an Opaque gate definition as a CalibrationDefinition"""
         # We can't do anything sensible with this yet, so it's better to loudly say that.
@@ -604,7 +630,7 @@ def build_gate_definition(self, gate):
 
         self.push_context(gate.definition)
         signature = self.build_gate_signature(gate)
-        body = ast.QuantumBlock(self.build_quantum_instructions(gate.definition.data))
+        body = ast.QuantumBlock(self.build_current_scope())
         self.pop_context()
         return ast.QuantumGateDefinition(signature, body)
 
@@ -627,8 +653,10 @@ def build_gate_signature(self, gate):
         ]
         return ast.QuantumGateSignature(ast.Identifier(name), quantum_arguments, params or None)
 
-    def build_parameter_declarations(self):
-        """Builds lists of the input, output and standard variables used in this program."""
+    def hoist_global_parameter_declarations(self):
+        """Extend ``self._global_io_declarations`` and ``self._global_classical_declarations`` with
+        any implicit declarations used to support the early IBM efforts to use :class:`.Parameter`
+        as an input variable."""
         global_scope = self.global_scope(assert_=True)
         for parameter in global_scope.circuit.parameters:
             parameter_name = self._register_variable(parameter, global_scope)
@@ -640,11 +668,13 @@ def build_parameter_declarations(self):
             if isinstance(declaration, ast.IODeclaration):
                 self._global_io_declarations.append(declaration)
             else:
-                self._global_classical_declarations.append(declaration)
+                self._global_classical_forward_declarations.append(declaration)
 
-    def build_classical_declarations(self):
-        """Extend the global classical declarations with AST nodes declaring all the classical bits
-        and registers.
+    def hoist_classical_register_declarations(self):
+        """Extend the global classical declarations with AST nodes declaring all the global-scope
+        circuit :class:`.Clbit` and :class:`.ClassicalRegister` instances.  Qiskit's data model
+        doesn't involve the declaration of *new* bits or registers in inner scopes; only the
+        :class:`.expr.Var` mechanism allows that.
 
         The behaviour of this function depends on the setting ``allow_aliasing``. If this
         is ``True``, then the output will be in the same form as the output of
@@ -670,12 +700,14 @@ def build_classical_declarations(self):
                 )
                 for i, clbit in enumerate(scope.circuit.clbits)
             )
-            self._global_classical_declarations.extend(clbits)
-            self._global_classical_declarations.extend(self.build_aliases(scope.circuit.cregs))
+            self._global_classical_forward_declarations.extend(clbits)
+            self._global_classical_forward_declarations.extend(
+                self.build_aliases(scope.circuit.cregs)
+            )
             return
         # If we're here, we're in the clbit happy path where there are no clbits that are in more
         # than one register.  We can output things very naturally.
-        self._global_classical_declarations.extend(
+        self._global_classical_forward_declarations.extend(
             ast.ClassicalDeclaration(
                 ast.BitType(),
                 self._register_variable(
@@ -691,10 +723,26 @@ def build_classical_declarations(self):
                 scope.symbol_map[bit] = ast.SubscriptedIdentifier(
                     name.string, ast.IntegerLiteral(i)
                 )
-            self._global_classical_declarations.append(
+            self._global_classical_forward_declarations.append(
                 ast.ClassicalDeclaration(ast.BitArrayType(len(register)), name)
             )
 
+    def hoist_classical_io_var_declarations(self):
+        """Hoist the declarations of classical IO :class:`.expr.Var` nodes into the global state.
+
+        Local :class:`.expr.Var` declarations are handled by the regular local-block scope builder,
+        and the :class:`.QuantumCircuit` data model ensures that the only time an IO variable can
+        occur is in an outermost block."""
+        scope = self.global_scope(assert_=True)
+        for var in scope.circuit.iter_input_vars():
+            self._global_io_declarations.append(
+                ast.IODeclaration(
+                    ast.IOModifier.INPUT,
+                    _build_ast_type(var.type),
+                    self._register_variable(var, scope),
+                )
+            )
+
     def build_quantum_declarations(self):
         """Return a list of AST nodes declaring all the qubits in the current scope, and all the
         alias declarations for these qubits."""
@@ -760,21 +808,37 @@ def build_aliases(self, registers: Iterable[Register]) -> List[ast.AliasStatemen
             out.append(ast.AliasStatement(name, ast.IndexSet(elements)))
         return out
 
-    def build_quantum_instructions(self, instructions):
-        """Builds a list of call statements"""
-        ret = []
-        for instruction in instructions:
-            if isinstance(instruction.operation, ForLoopOp):
-                ret.append(self.build_for_loop(instruction))
-                continue
-            if isinstance(instruction.operation, WhileLoopOp):
-                ret.append(self.build_while_loop(instruction))
-                continue
-            if isinstance(instruction.operation, IfElseOp):
-                ret.append(self.build_if_statement(instruction))
-                continue
-            if isinstance(instruction.operation, SwitchCaseOp):
-                ret.extend(self.build_switch_statement(instruction))
+    def build_current_scope(self) -> List[ast.Statement]:
+        """Build the instructions that occur in the current scope.
+
+        In addition to everything literally in the circuit's ``data`` field, this also includes
+        declarations for any local :class:`.expr.Var` nodes.
+        """
+        scope = self.current_scope()
+
+        # We forward-declare all local variables uninitialised at the top of their scope. It would
+        # be nice to declare the variable at the point of first store (so we can write things like
+        # `uint[8] a = 12;`), but there's lots of edge-case logic to catch with that around
+        # use-before-definition errors in the OQ3 output, for example if the user has side-stepped
+        # the `QuantumCircuit` API protection to produce a circuit that uses an uninitialised
+        # variable, or the initial write to a variable is within a control-flow scope.  (It would be
+        # easier to see the def/use chain needed to do this cleanly if we were using `DAGCircuit`.)
+        statements = [
+            ast.ClassicalDeclaration(_build_ast_type(var.type), self._register_variable(var, scope))
+            for var in scope.circuit.iter_declared_vars()
+        ]
+        for instruction in scope.circuit.data:
+            if isinstance(instruction.operation, ControlFlowOp):
+                if isinstance(instruction.operation, ForLoopOp):
+                    statements.append(self.build_for_loop(instruction))
+                elif isinstance(instruction.operation, WhileLoopOp):
+                    statements.append(self.build_while_loop(instruction))
+                elif isinstance(instruction.operation, IfElseOp):
+                    statements.append(self.build_if_statement(instruction))
+                elif isinstance(instruction.operation, SwitchCaseOp):
+                    statements.extend(self.build_switch_statement(instruction))
+                else:  # pragma: no cover
+                    raise RuntimeError(f"unhandled control-flow construct: {instruction.operation}")
                 continue
             # Build the node, ignoring any condition.
             if isinstance(instruction.operation, Gate):
@@ -795,6 +859,13 @@ def build_quantum_instructions(self, instructions):
                 ]
             elif isinstance(instruction.operation, Delay):
                 nodes = [self.build_delay(instruction)]
+            elif isinstance(instruction.operation, Store):
+                nodes = [
+                    ast.AssignmentStatement(
+                        self.build_expression(instruction.operation.lvalue),
+                        self.build_expression(instruction.operation.rvalue),
+                    )
+                ]
             elif isinstance(instruction.operation, BreakLoopOp):
                 nodes = [ast.BreakStatement()]
             elif isinstance(instruction.operation, ContinueLoopOp):
@@ -803,16 +874,16 @@ def build_quantum_instructions(self, instructions):
                 nodes = [self.build_subroutine_call(instruction)]
 
             if instruction.operation.condition is None:
-                ret.extend(nodes)
+                statements.extend(nodes)
             else:
                 body = ast.ProgramBlock(nodes)
-                ret.append(
+                statements.append(
                     ast.BranchingStatement(
                         self.build_expression(_lift_condition(instruction.operation.condition)),
                         body,
                     )
                 )
-        return ret
+        return statements
 
     def build_if_statement(self, instruction: CircuitInstruction) -> ast.BranchingStatement:
         """Build an :obj:`.IfElseOp` into a :obj:`.ast.BranchingStatement`."""
@@ -820,14 +891,14 @@ def build_if_statement(self, instruction: CircuitInstruction) -> ast.BranchingSt
 
         true_circuit = instruction.operation.blocks[0]
         self.push_scope(true_circuit, instruction.qubits, instruction.clbits)
-        true_body = self.build_program_block(true_circuit.data)
+        true_body = ast.ProgramBlock(self.build_current_scope())
         self.pop_scope()
         if len(instruction.operation.blocks) == 1:
             return ast.BranchingStatement(condition, true_body, None)
 
         false_circuit = instruction.operation.blocks[1]
         self.push_scope(false_circuit, instruction.qubits, instruction.clbits)
-        false_body = self.build_program_block(false_circuit.data)
+        false_body = ast.ProgramBlock(self.build_current_scope())
         self.pop_scope()
         return ast.BranchingStatement(condition, true_body, false_body)
 
@@ -838,7 +909,7 @@ def build_switch_statement(self, instruction: CircuitInstruction) -> Iterable[as
         target = self._reserve_variable_name(
             ast.Identifier(self._unique_name("switch_dummy", global_scope)), global_scope
         )
-        self._global_classical_declarations.append(
+        self._global_classical_forward_declarations.append(
             ast.ClassicalDeclaration(ast.IntType(), target, None)
         )
 
@@ -851,7 +922,7 @@ def case(values, case_block):
                     for v in values
                 ]
                 self.push_scope(case_block, instruction.qubits, instruction.clbits)
-                case_body = self.build_program_block(case_block.data)
+                case_body = ast.ProgramBlock(self.build_current_scope())
                 self.pop_scope()
                 return values, case_body
 
@@ -871,7 +942,7 @@ def case(values, case_block):
         default = None
         for values, block in instruction.operation.cases_specifier():
             self.push_scope(block, instruction.qubits, instruction.clbits)
-            case_body = self.build_program_block(block.data)
+            case_body = ast.ProgramBlock(self.build_current_scope())
             self.pop_scope()
             if CASE_DEFAULT in values:
                 # Even if it's mixed in with other cases, we can skip them and only output the
@@ -891,7 +962,7 @@ def build_while_loop(self, instruction: CircuitInstruction) -> ast.WhileLoopStat
         condition = self.build_expression(_lift_condition(instruction.operation.condition))
         loop_circuit = instruction.operation.blocks[0]
         self.push_scope(loop_circuit, instruction.qubits, instruction.clbits)
-        loop_body = self.build_program_block(loop_circuit.data)
+        loop_body = ast.ProgramBlock(self.build_current_scope())
         self.pop_scope()
         return ast.WhileLoopStatement(condition, loop_body)
 
@@ -921,7 +992,7 @@ def build_for_loop(self, instruction: CircuitInstruction) -> ast.ForLoopStatemen
                     "The values in OpenQASM 3 'for' loops must all be integers, but received"
                     f" '{indexset}'."
                 ) from None
-        body_ast = self.build_program_block(loop_circuit)
+        body_ast = ast.ProgramBlock(self.build_current_scope())
         self.pop_scope()
         return ast.ForLoopStatement(indexset_ast, loop_parameter_ast, body_ast)
 
@@ -961,10 +1032,6 @@ def build_integer(self, value) -> ast.IntegerLiteral:
             raise QASM3ExporterError(f"'{value}' is not an integer")  # pragma: no cover
         return ast.IntegerLiteral(int(value))
 
-    def build_program_block(self, instructions):
-        """Builds a ProgramBlock"""
-        return ast.ProgramBlock(self.build_quantum_instructions(instructions))
-
     def _rebind_scoped_parameters(self, expression):
         """If the input is a :class:`.ParameterExpression`, rebind any internal
         :class:`.Parameter`\\ s so that their names match their names in the scope.  Other inputs
@@ -1008,8 +1075,8 @@ def _infer_variable_declaration(
 
     This is very simplistic; it assumes all parameters are real numbers that need to be input to the
     program, unless one is used as a loop variable, in which case it shouldn't be declared at all,
-    because the ``for`` loop declares it implicitly (per the Qiskit/QSS reading of the OpenQASM
-    spec at Qiskit/openqasm@8ee55ec).
+    because the ``for`` loop declares it implicitly (per the Qiskit/qe-compiler reading of the
+    OpenQASM spec at openqasm/openqasm@8ee55ec).
 
     .. note::
 
diff --git a/releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml b/releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml
new file mode 100644
index 000000000000..217fbc464121
--- /dev/null
+++ b/releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    :class:`.Parameter` instances used as stand-ins for ``input`` variables in
+    OpenQASM 3 programs will now have their names escaped to avoid collisions
+    with built-in gates during the export to OpenQASM 3.  Previously there
+    could be a naming clash, and the exporter would generate invalid OpenQASM 3.
diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py
index 3bb1667992a1..8589576441a2 100644
--- a/test/python/qasm3/test_export.py
+++ b/test/python/qasm3/test_export.py
@@ -24,7 +24,7 @@
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile
 from qiskit.circuit import Parameter, Qubit, Clbit, Instruction, Gate, Delay, Barrier
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.controlflow import CASE_DEFAULT
 from qiskit.qasm3 import Exporter, dumps, dump, QASM3ExporterError, ExperimentalFeatures
 from qiskit.qasm3.exporter import QASM3Builder
@@ -948,7 +948,7 @@ def test_old_alias_classical_registers_option(self):
 
     def test_simple_for_loop(self):
         """Test that a simple for loop outputs the expected result."""
-        parameter = Parameter("x")
+        parameter = Parameter("my_x")
         loop_body = QuantumCircuit(1)
         loop_body.rx(parameter, 0)
         loop_body.break_loop()
@@ -978,8 +978,8 @@ def test_simple_for_loop(self):
 
     def test_nested_for_loop(self):
         """Test that a for loop nested inside another outputs the expected result."""
-        inner_parameter = Parameter("x")
-        outer_parameter = Parameter("y")
+        inner_parameter = Parameter("my_x")
+        outer_parameter = Parameter("my_y")
 
         inner_body = QuantumCircuit(2)
         inner_body.rz(inner_parameter, 0)
@@ -1024,9 +1024,9 @@ def test_nested_for_loop(self):
     def test_regular_parameter_in_nested_for_loop(self):
         """Test that a for loop nested inside another outputs the expected result, including
         defining parameters that are used in nested loop scopes."""
-        inner_parameter = Parameter("x")
-        outer_parameter = Parameter("y")
-        regular_parameter = Parameter("t")
+        inner_parameter = Parameter("my_x")
+        outer_parameter = Parameter("my_y")
+        regular_parameter = Parameter("my_t")
 
         inner_body = QuantumCircuit(2)
         inner_body.h(0)
@@ -1471,6 +1471,17 @@ def test_parameters_and_registers_cannot_have_naming_clashes(self):
         self.assertIn("clash", parameter_name["name"])
         self.assertNotEqual(register_name["name"], parameter_name["name"])
 
+    def test_parameters_and_gates_cannot_have_naming_clashes(self):
+        """Test that parameters are renamed to avoid collisions with gate names."""
+        qc = QuantumCircuit(QuantumRegister(1, "q"))
+        qc.rz(Parameter("rz"), 0)
+
+        out_qasm = dumps(qc)
+        parameter_name = self.scalar_parameter_regex.search(out_qasm)
+        self.assertTrue(parameter_name)
+        self.assertIn("rz", parameter_name["name"])
+        self.assertNotEqual(parameter_name["name"], "rz")
+
     # Not necessarily all the reserved keywords, just a sensibly-sized subset.
     @data("bit", "const", "def", "defcal", "float", "gate", "include", "int", "let", "measure")
     def test_reserved_keywords_as_names_are_escaped(self, keyword):
@@ -1736,6 +1747,145 @@ def test_no_unnecessary_cast(self):
 bit[8] cr;
 if (cr == 1) {
 }
+"""
+        self.assertEqual(dumps(qc), expected)
+
+    def test_var_use(self):
+        """Test that input and declared vars work in simple local scopes and can be set."""
+        qc = QuantumCircuit()
+        a = qc.add_input("a", types.Bool())
+        b = qc.add_input("b", types.Uint(8))
+        qc.store(a, expr.logic_not(a))
+        qc.store(b, expr.bit_and(b, 8))
+        qc.add_var("c", expr.bit_not(b))
+        # All inputs should come first, regardless of declaration order.
+        qc.add_input("d", types.Bool())
+
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+input bool a;
+input uint[8] b;
+input bool d;
+uint[8] c;
+a = !a;
+b = b & 8;
+c = ~b;
+"""
+        self.assertEqual(dumps(qc), expected)
+
+    def test_var_use_in_scopes(self):
+        """Test that usage of `Var` nodes works in capturing scopes."""
+        qc = QuantumCircuit(2, 2)
+        a = qc.add_input("a", types.Bool())
+        b_outer = qc.add_var("b", expr.lift(5, types.Uint(16)))
+        with qc.if_test(expr.logic_not(a)) as else_:
+            qc.store(b_outer, expr.bit_not(b_outer))
+            qc.h(0)
+        with else_:
+            # Shadow of the same type.
+            qc.add_var("b", expr.lift(7, b_outer.type))
+        with qc.while_loop(a):
+            # Shadow of a different type.
+            qc.add_var("b", a)
+        with qc.switch(b_outer) as case:
+            with case(0):
+                qc.store(b_outer, expr.lift(3, b_outer.type))
+            with case(case.DEFAULT):
+                qc.add_var("b", expr.logic_not(a))
+                qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+input bool a;
+bit[2] c;
+int switch_dummy;
+qubit[2] q;
+uint[16] b;
+b = 5;
+if (!a) {
+  b = ~b;
+  h q[0];
+} else {
+  uint[16] b;
+  b = 7;
+}
+while (a) {
+  bool b;
+  b = a;
+}
+switch_dummy = b;
+switch (switch_dummy) {
+  case 0 {
+    b = 3;
+  }
+  default {
+    bool b;
+    b = !a;
+    cx q[0], q[1];
+  }
+}
+c[0] = measure q[0];
+c[1] = measure q[1];
+"""
+        self.assertEqual(dumps(qc), expected)
+
+    def test_var_naming_clash_parameter(self):
+        """We should support a `Var` clashing in name with a `Parameter` if `QuantumCircuit` allows
+        it."""
+        qc = QuantumCircuit(1)
+        qc.add_var("a", False)
+        qc.rx(Parameter("a"), 0)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+input float[64] a;
+qubit[1] q;
+bool a__generated0;
+a__generated0 = false;
+rx(a) q[0];
+"""
+        self.assertEqual(dumps(qc), expected)
+
+    def test_var_naming_clash_register(self):
+        """We should support a `Var` clashing in name with a `Register` if `QuantumCircuit` allows
+        it."""
+        qc = QuantumCircuit(QuantumRegister(2, "q"), ClassicalRegister(2, "c"))
+        qc.add_input("c", types.Bool())
+        qc.add_var("q", False)
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+input bool c__generated0;
+bit[2] c;
+qubit[2] q;
+bool q__generated1;
+q__generated1 = false;
+"""
+        self.assertEqual(dumps(qc), expected)
+
+    def test_var_naming_clash_gate(self):
+        """We should support a `Var` clashing in name with some gate if `QuantumCircuit` allows
+        it."""
+        qc = QuantumCircuit(2)
+        qc.add_input("cx", types.Bool())
+        qc.add_input("U", types.Bool())
+        qc.add_var("rx", expr.lift(5, types.Uint(8)))
+
+        qc.cx(0, 1)
+        qc.u(0.5, 0.125, 0.25, 0)
+        # We don't actually use `rx`, but it's still in the `stdgates` include.
+        expected = """\
+OPENQASM 3.0;
+include "stdgates.inc";
+input bool cx__generated0;
+input bool U__generated1;
+qubit[2] q;
+uint[8] rx__generated2;
+rx__generated2 = 5;
+cx q[0], q[1];
+U(0.5, 0.125, 0.25) q[0];
 """
         self.assertEqual(dumps(qc), expected)
 
@@ -2654,3 +2804,11 @@ def test_disallow_opaque_instruction(self):
             QASM3ExporterError, "Exporting opaque instructions .* is not yet supported"
         ):
             exporter.dumps(qc)
+
+    def test_disallow_export_of_inner_scope(self):
+        """A circuit with captures can't be a top-level OQ3 program."""
+        qc = QuantumCircuit(captures=[expr.Var.new("a", types.Bool())])
+        with self.assertRaisesRegex(
+            QASM3ExporterError, "cannot export an inner scope.*as a top-level program"
+        ):
+            dumps(qc)

From cadc6f17f549fb084067c3f79f44345a6214b523 Mon Sep 17 00:00:00 2001
From: Hiroshi Horii 
Date: Thu, 2 May 2024 20:39:22 +0900
Subject: [PATCH 054/179] fix bugs in example of EstimatorV2 (#12326)

---
 qiskit/primitives/__init__.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qiskit/primitives/__init__.py b/qiskit/primitives/__init__.py
index 0a36dbbb0bd3..2423f3545f80 100644
--- a/qiskit/primitives/__init__.py
+++ b/qiskit/primitives/__init__.py
@@ -86,17 +86,17 @@
     estimator = Estimator()
 
     # calculate [  ]
-    job = estimator.run([(psi1, hamiltonian1, [theta1])])
+    job = estimator.run([(psi1, H1, [theta1])])
     job_result = job.result() # It will block until the job finishes.
-    print(f"The primitive-job finished with result {job_result}"))
+    print(f"The primitive-job finished with result {job_result}")
 
     # calculate [ [,
     #              ],
     #             [] ]
     job2 = estimator.run(
         [
-            (psi1, [hamiltonian1, hamiltonian3], [theta1, theta3]), 
-            (psi2, hamiltonian2, theta2)
+            (psi1, [H1, H3], [theta1, theta3]), 
+            (psi2, H2, theta2)
         ],
         precision=0.01
     )

From d6c74c265ab4d5ed1d16694804d9fd7130003bb5 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 2 May 2024 14:25:04 +0100
Subject: [PATCH 055/179] Add indexing and bitshift expressions (#12310)

This adds the concepts of `Index` and the binary operations `SHIFT_LEFT`
and `SHIFT_RIGHT` to the `Expr` system, and threads them through all the
`ExprVisitor` nodes defined in Qiskit, including OQ3 and QPY
serialisation/deserialisation.  (The not-new OQ3 parser is managed
separately and doesn't have support for _any_ `Expr` nodes at the
moment).

Along with `Store`, this should close the gap between what Qiskit was
able to represent with dynamic circuits, and what was supported by
hardware with direct OpenQASM 3 submission, although since this remains
Qiskit's fairly low-level representation, it still was potentially more
ergonomic to use OpenQASM 3 strings.  This remains a general point for
improvement in the Qiskit API, however.
---
 qiskit/circuit/classical/expr/__init__.py     | 24 +++++-
 qiskit/circuit/classical/expr/constructors.py | 85 ++++++++++++++++++-
 qiskit/circuit/classical/expr/expr.py         | 41 +++++++++
 qiskit/circuit/classical/expr/visitors.py     | 20 +++++
 qiskit/qasm3/ast.py                           |  8 ++
 qiskit/qasm3/exporter.py                      |  3 +
 qiskit/qasm3/printer.py                       | 24 ++++--
 qiskit/qpy/__init__.py                        | 15 ++++
 qiskit/qpy/binary_io/circuits.py              | 18 ++--
 qiskit/qpy/binary_io/schedules.py             | 78 +++++++++--------
 qiskit/qpy/binary_io/value.py                 | 51 ++++++++---
 qiskit/qpy/type_keys.py                       |  1 +
 .../expr-bitshift-index-e9cfc6ea8729ef5e.yaml | 20 +++++
 .../classical/test_expr_constructors.py       | 54 ++++++++++++
 .../circuit/classical/test_expr_helpers.py    |  4 +
 .../circuit/classical/test_expr_properties.py | 15 ++++
 .../circuit/test_circuit_load_from_qpy.py     | 38 +++++++++
 test/python/circuit/test_store.py             | 28 ++++++
 test/python/qasm3/test_export.py              | 47 +++++++++-
 test/qpy_compat/test_qpy.py                   | 19 +++++
 20 files changed, 529 insertions(+), 64 deletions(-)
 create mode 100644 releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml

diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py
index 9884062c5f51..c0057ca96f02 100644
--- a/qiskit/circuit/classical/expr/__init__.py
+++ b/qiskit/circuit/classical/expr/__init__.py
@@ -45,7 +45,7 @@
 .. autoclass:: Var
     :members: var, name
 
-Similarly, literals used in comparison (such as integers) should be lifted to :class:`Value` nodes
+Similarly, literals used in expressions (such as integers) should be lifted to :class:`Value` nodes
 with associated types.
 
 .. autoclass:: Value
@@ -62,6 +62,12 @@
     :members: Op
     :member-order: bysource
 
+Bit-like types (unsigned integers) can be indexed by integer types, represented by :class:`Index`.
+The result is a single bit.  The resulting expression has an associated memory location (and so can
+be used as an lvalue for :class:`.Store`, etc) if the target is also an lvalue.
+
+.. autoclass:: Index
+
 When constructing expressions, one must ensure that the types are valid for the operation.
 Attempts to construct expressions with invalid types will raise a regular Python ``TypeError``.
 
@@ -122,6 +128,13 @@
 .. autofunction:: less_equal
 .. autofunction:: greater
 .. autofunction:: greater_equal
+.. autofunction:: shift_left
+.. autofunction:: shift_right
+
+You can index into unsigned integers and bit-likes using another unsigned integer of any width.
+This includes in storing operations, if the target of the index is writeable.
+
+.. autofunction:: index
 
 Qiskit's legacy method for specifying equality conditions for use in conditionals is to use a
 two-tuple of a :class:`.Clbit` or :class:`.ClassicalRegister` and an integer.  This represents an
@@ -174,6 +187,7 @@
     "Cast",
     "Unary",
     "Binary",
+    "Index",
     "ExprVisitor",
     "iter_vars",
     "structurally_equivalent",
@@ -185,6 +199,8 @@
     "bit_and",
     "bit_or",
     "bit_xor",
+    "shift_left",
+    "shift_right",
     "logic_and",
     "logic_or",
     "equal",
@@ -193,10 +209,11 @@
     "less_equal",
     "greater",
     "greater_equal",
+    "index",
     "lift_legacy_condition",
 ]
 
-from .expr import Expr, Var, Value, Cast, Unary, Binary
+from .expr import Expr, Var, Value, Cast, Unary, Binary, Index
 from .visitors import ExprVisitor, iter_vars, structurally_equivalent, is_lvalue
 from .constructors import (
     lift,
@@ -214,5 +231,8 @@
     less_equal,
     greater,
     greater_equal,
+    shift_left,
+    shift_right,
+    index,
     lift_legacy_condition,
 )
diff --git a/qiskit/circuit/classical/expr/constructors.py b/qiskit/circuit/classical/expr/constructors.py
index 64a19a2aee2a..de3875eef90c 100644
--- a/qiskit/circuit/classical/expr/constructors.py
+++ b/qiskit/circuit/classical/expr/constructors.py
@@ -37,7 +37,7 @@
 
 import typing
 
-from .expr import Expr, Var, Value, Unary, Binary, Cast
+from .expr import Expr, Var, Value, Unary, Binary, Cast, Index
 from ..types import CastKind, cast_kind
 from .. import types
 
@@ -471,3 +471,86 @@ def greater_equal(left: typing.Any, right: typing.Any, /) -> Expr:
 Uint(3))
     """
     return _binary_relation(Binary.Op.GREATER_EQUAL, left, right)
+
+
+def _shift_like(
+    op: Binary.Op, left: typing.Any, right: typing.Any, type: types.Type | None
+) -> Expr:
+    if type is not None and type.kind is not types.Uint:
+        raise TypeError(f"type '{type}' is not a valid bitshift operand type")
+    if isinstance(left, Expr):
+        left = _coerce_lossless(left, type) if type is not None else left
+    else:
+        left = lift(left, type)
+    right = lift(right)
+    if left.type.kind != types.Uint or right.type.kind != types.Uint:
+        raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'")
+    return Binary(op, left, right, left.type)
+
+
+def shift_left(left: typing.Any, right: typing.Any, /, type: types.Type | None = None) -> Expr:
+    """Create a 'bitshift left' expression node from the given two values, resolving any implicit
+    casts and lifting the values into :class:`Value` nodes if required.
+
+    If ``type`` is given, the ``left`` operand will be coerced to it (if possible).
+
+    Examples:
+        Shift the value of a standalone variable left by some amount::
+
+            >>> from qiskit.circuit.classical import expr, types
+            >>> a = expr.Var.new("a", types.Uint(8))
+            >>> expr.shift_left(a, 4)
+            Binary(Binary.Op.SHIFT_LEFT, \
+Var(, Uint(8), name='a'), \
+Value(4, Uint(3)), \
+Uint(8))
+
+        Shift an integer literal by a variable amount, coercing the type of the literal::
+
+            >>> expr.shift_left(3, a, types.Uint(16))
+            Binary(Binary.Op.SHIFT_LEFT, \
+Value(3, Uint(16)), \
+Var(, Uint(8), name='a'), \
+Uint(16))
+    """
+    return _shift_like(Binary.Op.SHIFT_LEFT, left, right, type)
+
+
+def shift_right(left: typing.Any, right: typing.Any, /, type: types.Type | None = None) -> Expr:
+    """Create a 'bitshift right' expression node from the given values, resolving any implicit casts
+    and lifting the values into :class:`Value` nodes if required.
+
+    If ``type`` is given, the ``left`` operand will be coerced to it (if possible).
+
+    Examples:
+        Shift the value of a classical register right by some amount::
+
+            >>> from qiskit.circuit import ClassicalRegister
+            >>> from qiskit.circuit.classical import expr
+            >>> expr.shift_right(ClassicalRegister(8, "a"), 4)
+            Binary(Binary.Op.SHIFT_RIGHT, \
+Var(ClassicalRegister(8, "a"), Uint(8)), \
+Value(4, Uint(3)), \
+Uint(8))
+    """
+    return _shift_like(Binary.Op.SHIFT_RIGHT, left, right, type)
+
+
+def index(target: typing.Any, index: typing.Any, /) -> Expr:
+    """Index into the ``target`` with the given integer ``index``, lifting the values into
+    :class:`Value` nodes if required.
+
+    This can be used as the target of a :class:`.Store`, if the ``target`` is itself an lvalue.
+
+    Examples:
+        Index into a classical register with a literal::
+
+            >>> from qiskit.circuit import ClassicalRegister
+            >>> from qiskit.circuit.classical import expr
+            >>> expr.index(ClassicalRegister(8, "a"), 3)
+            Index(Var(ClassicalRegister(8, "a"), Uint(8)), Value(3, Uint(2)), Bool())
+    """
+    target, index = lift(target), lift(index)
+    if target.type.kind is not types.Uint or index.type.kind is not types.Uint:
+        raise TypeError(f"invalid types for indexing: '{target.type}' and '{index.type}'")
+    return Index(target, index, types.Bool())
diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py
index c22870e51fee..62b6829ce4a7 100644
--- a/qiskit/circuit/classical/expr/expr.py
+++ b/qiskit/circuit/classical/expr/expr.py
@@ -300,6 +300,11 @@ class Op(enum.Enum):
         The binary mathematical relations :data:`EQUAL`, :data:`NOT_EQUAL`, :data:`LESS`,
         :data:`LESS_EQUAL`, :data:`GREATER` and :data:`GREATER_EQUAL` take unsigned integers
         (with an implicit cast to make them the same width), and return a Boolean.
+
+        The bitshift operations :data:`SHIFT_LEFT` and :data:`SHIFT_RIGHT` can take bit-like
+        container types (e.g. unsigned integers) as the left operand, and any integer type as the
+        right-hand operand.  In all cases, the output bit width is the same as the input, and zeros
+        fill in the "exposed" spaces.
         """
 
         # If adding opcodes, remember to add helper constructor functions in `constructors.py`
@@ -327,6 +332,10 @@ class Op(enum.Enum):
         """Numeric greater than. ``lhs > rhs``."""
         GREATER_EQUAL = 11
         """Numeric greater than or equal to. ``lhs >= rhs``."""
+        SHIFT_LEFT = 12
+        """Zero-padding bitshift to the left.  ``lhs << rhs``."""
+        SHIFT_RIGHT = 13
+        """Zero-padding bitshift to the right.  ``lhs >> rhs``."""
 
         def __str__(self):
             return f"Binary.{super().__str__()}"
@@ -354,3 +363,35 @@ def __eq__(self, other):
 
     def __repr__(self):
         return f"Binary({self.op}, {self.left}, {self.right}, {self.type})"
+
+
+@typing.final
+class Index(Expr):
+    """An indexing expression.
+
+    Args:
+        target: The object being indexed.
+        index: The expression doing the indexing.
+        type: The resolved type of the result.
+    """
+
+    __slots__ = ("target", "index")
+
+    def __init__(self, target: Expr, index: Expr, type: types.Type):
+        self.target = target
+        self.index = index
+        self.type = type
+
+    def accept(self, visitor, /):
+        return visitor.visit_index(self)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, Index)
+            and self.type == other.type
+            and self.target == other.target
+            and self.index == other.index
+        )
+
+    def __repr__(self):
+        return f"Index({self.target}, {self.index}, {self.type})"
diff --git a/qiskit/circuit/classical/expr/visitors.py b/qiskit/circuit/classical/expr/visitors.py
index c0c1a5894af6..744257714b79 100644
--- a/qiskit/circuit/classical/expr/visitors.py
+++ b/qiskit/circuit/classical/expr/visitors.py
@@ -55,6 +55,9 @@ def visit_binary(self, node: expr.Binary, /) -> _T_co:  # pragma: no cover
     def visit_cast(self, node: expr.Cast, /) -> _T_co:  # pragma: no cover
         return self.visit_generic(node)
 
+    def visit_index(self, node: expr.Index, /) -> _T_co:  # pragma: no cover
+        return self.visit_generic(node)
+
 
 class _VarWalkerImpl(ExprVisitor[typing.Iterable[expr.Var]]):
     __slots__ = ()
@@ -75,6 +78,10 @@ def visit_binary(self, node, /):
     def visit_cast(self, node, /):
         yield from node.operand.accept(self)
 
+    def visit_index(self, node, /):
+        yield from node.target.accept(self)
+        yield from node.index.accept(self)
+
 
 _VAR_WALKER = _VarWalkerImpl()
 
@@ -164,6 +171,16 @@ def visit_cast(self, node, /):
         self.other = self.other.operand
         return node.operand.accept(self)
 
+    def visit_index(self, node, /):
+        if self.other.__class__ is not node.__class__ or self.other.type != node.type:
+            return False
+        other = self.other
+        self.other = other.target
+        if not node.target.accept(self):
+            return False
+        self.other = other.index
+        return node.index.accept(self)
+
 
 def structurally_equivalent(
     left: expr.Expr,
@@ -235,6 +252,9 @@ def visit_binary(self, node, /):
     def visit_cast(self, node, /):
         return False
 
+    def visit_index(self, node, /):
+        return node.target.accept(self)
+
 
 _IS_LVALUE = _IsLValueImpl()
 
diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py
index 7674eace89db..300c53900d43 100644
--- a/qiskit/qasm3/ast.py
+++ b/qiskit/qasm3/ast.py
@@ -252,6 +252,8 @@ class Op(enum.Enum):
         GREATER_EQUAL = ">="
         EQUAL = "=="
         NOT_EQUAL = "!="
+        SHIFT_LEFT = "<<"
+        SHIFT_RIGHT = ">>"
 
     def __init__(self, op: Op, left: Expression, right: Expression):
         self.op = op
@@ -265,6 +267,12 @@ def __init__(self, type: ClassicalType, operand: Expression):
         self.operand = operand
 
 
+class Index(Expression):
+    def __init__(self, target: Expression, index: Expression):
+        self.target = target
+        self.index = index
+
+
 class IndexSet(ASTNode):
     """
     A literal index set of values::
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index b85e35e22a50..16bc2529ca74 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -1165,3 +1165,6 @@ def visit_binary(self, node, /):
         return ast.Binary(
             ast.Binary.Op[node.op.name], node.left.accept(self), node.right.accept(self)
         )
+
+    def visit_index(self, node, /):
+        return ast.Index(node.target.accept(self), node.index.accept(self))
diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py
index ba253144a168..58f689c2c2e7 100644
--- a/qiskit/qasm3/printer.py
+++ b/qiskit/qasm3/printer.py
@@ -34,13 +34,16 @@
 # indexing and casting are all higher priority than these, so we just ignore them.
 _BindingPower = collections.namedtuple("_BindingPower", ("left", "right"), defaults=(255, 255))
 _BINDING_POWER = {
-    # Power: (21, 22)
+    # Power: (24, 23)
     #
-    ast.Unary.Op.LOGIC_NOT: _BindingPower(right=20),
-    ast.Unary.Op.BIT_NOT: _BindingPower(right=20),
+    ast.Unary.Op.LOGIC_NOT: _BindingPower(right=22),
+    ast.Unary.Op.BIT_NOT: _BindingPower(right=22),
     #
-    # Multiplication/division/modulo: (17, 18)
-    # Addition/subtraction: (15, 16)
+    # Multiplication/division/modulo: (19, 20)
+    # Addition/subtraction: (17, 18)
+    #
+    ast.Binary.Op.SHIFT_LEFT: _BindingPower(15, 16),
+    ast.Binary.Op.SHIFT_RIGHT: _BindingPower(15, 16),
     #
     ast.Binary.Op.LESS: _BindingPower(13, 14),
     ast.Binary.Op.LESS_EQUAL: _BindingPower(13, 14),
@@ -332,6 +335,17 @@ def _visit_Cast(self, node: ast.Cast):
         self.visit(node.operand)
         self.stream.write(")")
 
+    def _visit_Index(self, node: ast.Index):
+        if isinstance(node.target, (ast.Unary, ast.Binary)):
+            self.stream.write("(")
+            self.visit(node.target)
+            self.stream.write(")")
+        else:
+            self.visit(node.target)
+        self.stream.write("[")
+        self.visit(node.index)
+        self.stream.write("]")
+
     def _visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration) -> None:
         self._start_line()
         self.visit(node.type)
diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py
index 7851db5c2a10..d7275bcd62f4 100644
--- a/qiskit/qpy/__init__.py
+++ b/qiskit/qpy/__init__.py
@@ -382,6 +382,21 @@
 Notably, this new type-code indexes into pre-defined variables from the circuit header, rather than
 redefining the variable again in each location it is used.
 
+
+Changes to EXPRESSION
+---------------------
+
+The EXPRESSION type code has a new possible entry, ``i``, corresponding to :class:`.expr.Index`
+nodes.
+
+======================  =========  =======================================================  ========
+Qiskit class            Type code  Payload                                                  Children
+======================  =========  =======================================================  ========
+:class:`~.expr.Index`   ``i``      No additional payload. The children are the target       2
+                                   and the index, in that order.
+======================  =========  =======================================================  ========
+
+
 .. _qpy_version_11:
 
 Version 11
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
index 1cf003ff3585..25103e7b4c27 100644
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -696,6 +696,7 @@ def _dumps_instruction_parameter(
             index_map=index_map,
             use_symengine=use_symengine,
             standalone_var_indices=standalone_var_indices,
+            version=version,
         )
 
     return type_key, data_bytes
@@ -808,6 +809,7 @@ def _write_instruction(
         value.write_value(
             file_obj,
             op_condition,
+            version=version,
             index_map=index_map,
             standalone_var_indices=standalone_var_indices,
         )
@@ -837,7 +839,7 @@ def _write_instruction(
     return custom_operations_list
 
 
-def _write_pauli_evolution_gate(file_obj, evolution_gate):
+def _write_pauli_evolution_gate(file_obj, evolution_gate, version):
     operator_list = evolution_gate.operator
     standalone = False
     if not isinstance(operator_list, list):
@@ -856,7 +858,7 @@ def _write_elem(buffer, op):
         data = common.data_to_binary(operator, _write_elem)
         pauli_data_buf.write(data)
 
-    time_type, time_data = value.dumps_value(evolution_gate.time)
+    time_type, time_data = value.dumps_value(evolution_gate.time, version=version)
     time_size = len(time_data)
     synth_class = str(type(evolution_gate.synthesis).__name__)
     settings_dict = evolution_gate.synthesis.settings
@@ -918,7 +920,7 @@ def _write_custom_operation(
 
     if type_key == type_keys.CircuitInstruction.PAULI_EVOL_GATE:
         has_definition = True
-        data = common.data_to_binary(operation, _write_pauli_evolution_gate)
+        data = common.data_to_binary(operation, _write_pauli_evolution_gate, version=version)
         size = len(data)
     elif type_key == type_keys.CircuitInstruction.CONTROLLED_GATE:
         # For ControlledGate, we have to access and store the private `_definition` rather than the
@@ -976,7 +978,7 @@ def _write_custom_operation(
     return new_custom_instruction
 
 
-def _write_calibrations(file_obj, calibrations, metadata_serializer):
+def _write_calibrations(file_obj, calibrations, metadata_serializer, version):
     flatten_dict = {}
     for gate, caldef in calibrations.items():
         for (qubits, params), schedule in caldef.items():
@@ -1000,8 +1002,8 @@ def _write_calibrations(file_obj, calibrations, metadata_serializer):
         for qubit in qubits:
             file_obj.write(struct.pack("!q", qubit))
         for param in params:
-            value.write_value(file_obj, param)
-        schedules.write_schedule_block(file_obj, schedule, metadata_serializer)
+            value.write_value(file_obj, param, version=version)
+        schedules.write_schedule_block(file_obj, schedule, metadata_serializer, version=version)
 
 
 def _write_registers(file_obj, in_circ_regs, full_bits):
@@ -1211,7 +1213,7 @@ def write_circuit(
     metadata_size = len(metadata_raw)
     num_instructions = len(circuit)
     circuit_name = circuit.name.encode(common.ENCODE)
-    global_phase_type, global_phase_data = value.dumps_value(circuit.global_phase)
+    global_phase_type, global_phase_data = value.dumps_value(circuit.global_phase, version=version)
 
     with io.BytesIO() as reg_buf:
         num_qregs = _write_registers(reg_buf, circuit.qregs, circuit.qubits)
@@ -1305,7 +1307,7 @@ def write_circuit(
     instruction_buffer.close()
 
     # Write calibrations
-    _write_calibrations(file_obj, circuit.calibrations, metadata_serializer)
+    _write_calibrations(file_obj, circuit.calibrations, metadata_serializer, version=version)
     _write_layout(file_obj, circuit)
 
 
diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py
index a94372f3bfc5..1a3393b1e4c8 100644
--- a/qiskit/qpy/binary_io/schedules.py
+++ b/qiskit/qpy/binary_io/schedules.py
@@ -328,7 +328,7 @@ def _read_element(file_obj, version, metadata_deserializer, use_symengine):
     return instance
 
 
-def _loads_reference_item(type_key, data_bytes, version, metadata_deserializer):
+def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version):
     if type_key == type_keys.Value.NULL:
         return None
     if type_key == type_keys.Program.SCHEDULE_BLOCK:
@@ -346,13 +346,13 @@ def _loads_reference_item(type_key, data_bytes, version, metadata_deserializer):
     )
 
 
-def _write_channel(file_obj, data):
+def _write_channel(file_obj, data, version):
     type_key = type_keys.ScheduleChannel.assign(data)
     common.write_type_key(file_obj, type_key)
-    value.write_value(file_obj, data.index)
+    value.write_value(file_obj, data.index, version=version)
 
 
-def _write_waveform(file_obj, data):
+def _write_waveform(file_obj, data, version):
     samples_bytes = common.data_to_binary(data.samples, np.save)
 
     header = struct.pack(
@@ -363,39 +363,43 @@ def _write_waveform(file_obj, data):
     )
     file_obj.write(header)
     file_obj.write(samples_bytes)
-    value.write_value(file_obj, data.name)
+    value.write_value(file_obj, data.name, version=version)
 
 
-def _dumps_obj(obj):
+def _dumps_obj(obj, version):
     """Wraps `value.dumps_value` to serialize dictionary and list objects
     which are not supported by `value.dumps_value`.
     """
     if isinstance(obj, dict):
         with BytesIO() as container:
-            common.write_mapping(file_obj=container, mapping=obj, serializer=_dumps_obj)
+            common.write_mapping(
+                file_obj=container, mapping=obj, serializer=_dumps_obj, version=version
+            )
             binary_data = container.getvalue()
         return b"D", binary_data
     elif isinstance(obj, list):
         with BytesIO() as container:
-            common.write_sequence(file_obj=container, sequence=obj, serializer=_dumps_obj)
+            common.write_sequence(
+                file_obj=container, sequence=obj, serializer=_dumps_obj, version=version
+            )
             binary_data = container.getvalue()
         return b"l", binary_data
     else:
-        return value.dumps_value(obj)
+        return value.dumps_value(obj, version=version)
 
 
-def _write_kernel(file_obj, data):
+def _write_kernel(file_obj, data, version):
     name = data.name
     params = data.params
-    common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj)
-    value.write_value(file_obj, name)
+    common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj, version=version)
+    value.write_value(file_obj, name, version=version)
 
 
-def _write_discriminator(file_obj, data):
+def _write_discriminator(file_obj, data, version):
     name = data.name
     params = data.params
-    common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj)
-    value.write_value(file_obj, name)
+    common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj, version=version)
+    value.write_value(file_obj, name, version=version)
 
 
 def _dumps_symbolic_expr(expr, use_symengine):
@@ -410,7 +414,7 @@ def _dumps_symbolic_expr(expr, use_symengine):
     return zlib.compress(expr_bytes)
 
 
-def _write_symbolic_pulse(file_obj, data, use_symengine):
+def _write_symbolic_pulse(file_obj, data, use_symengine, version):
     class_name_bytes = data.__class__.__name__.encode(common.ENCODE)
     pulse_type_bytes = data.pulse_type.encode(common.ENCODE)
     envelope_bytes = _dumps_symbolic_expr(data.envelope, use_symengine)
@@ -436,52 +440,51 @@ def _write_symbolic_pulse(file_obj, data, use_symengine):
         file_obj,
         mapping=data._params,
         serializer=value.dumps_value,
+        version=version,
     )
-    value.write_value(file_obj, data.duration)
-    value.write_value(file_obj, data.name)
+    value.write_value(file_obj, data.duration, version=version)
+    value.write_value(file_obj, data.name, version=version)
 
 
-def _write_alignment_context(file_obj, context):
+def _write_alignment_context(file_obj, context, version):
     type_key = type_keys.ScheduleAlignment.assign(context)
     common.write_type_key(file_obj, type_key)
     common.write_sequence(
-        file_obj,
-        sequence=context._context_params,
-        serializer=value.dumps_value,
+        file_obj, sequence=context._context_params, serializer=value.dumps_value, version=version
     )
 
 
-def _dumps_operand(operand, use_symengine):
+def _dumps_operand(operand, use_symengine, version):
     if isinstance(operand, library.Waveform):
         type_key = type_keys.ScheduleOperand.WAVEFORM
-        data_bytes = common.data_to_binary(operand, _write_waveform)
+        data_bytes = common.data_to_binary(operand, _write_waveform, version=version)
     elif isinstance(operand, library.SymbolicPulse):
         type_key = type_keys.ScheduleOperand.SYMBOLIC_PULSE
         data_bytes = common.data_to_binary(
-            operand, _write_symbolic_pulse, use_symengine=use_symengine
+            operand, _write_symbolic_pulse, use_symengine=use_symengine, version=version
         )
     elif isinstance(operand, channels.Channel):
         type_key = type_keys.ScheduleOperand.CHANNEL
-        data_bytes = common.data_to_binary(operand, _write_channel)
+        data_bytes = common.data_to_binary(operand, _write_channel, version=version)
     elif isinstance(operand, str):
         type_key = type_keys.ScheduleOperand.OPERAND_STR
         data_bytes = operand.encode(common.ENCODE)
     elif isinstance(operand, Kernel):
         type_key = type_keys.ScheduleOperand.KERNEL
-        data_bytes = common.data_to_binary(operand, _write_kernel)
+        data_bytes = common.data_to_binary(operand, _write_kernel, version=version)
     elif isinstance(operand, Discriminator):
         type_key = type_keys.ScheduleOperand.DISCRIMINATOR
-        data_bytes = common.data_to_binary(operand, _write_discriminator)
+        data_bytes = common.data_to_binary(operand, _write_discriminator, version=version)
     else:
-        type_key, data_bytes = value.dumps_value(operand)
+        type_key, data_bytes = value.dumps_value(operand, version=version)
 
     return type_key, data_bytes
 
 
-def _write_element(file_obj, element, metadata_serializer, use_symengine):
+def _write_element(file_obj, element, metadata_serializer, use_symengine, version):
     if isinstance(element, ScheduleBlock):
         common.write_type_key(file_obj, type_keys.Program.SCHEDULE_BLOCK)
-        write_schedule_block(file_obj, element, metadata_serializer, use_symengine)
+        write_schedule_block(file_obj, element, metadata_serializer, use_symengine, version=version)
     else:
         type_key = type_keys.ScheduleInstruction.assign(element)
         common.write_type_key(file_obj, type_key)
@@ -490,11 +493,12 @@ def _write_element(file_obj, element, metadata_serializer, use_symengine):
             sequence=element.operands,
             serializer=_dumps_operand,
             use_symengine=use_symengine,
+            version=version,
         )
-        value.write_value(file_obj, element.name)
+        value.write_value(file_obj, element.name, version=version)
 
 
-def _dumps_reference_item(schedule, metadata_serializer):
+def _dumps_reference_item(schedule, metadata_serializer, version):
     if schedule is None:
         type_key = type_keys.Value.NULL
         data_bytes = b""
@@ -504,6 +508,7 @@ def _dumps_reference_item(schedule, metadata_serializer):
             obj=schedule,
             serializer=write_schedule_block,
             metadata_serializer=metadata_serializer,
+            version=version,
         )
     return type_key, data_bytes
 
@@ -576,7 +581,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen
 
 def write_schedule_block(
     file_obj, block, metadata_serializer=None, use_symengine=False, version=common.QPY_VERSION
-):  # pylint: disable=unused-argument
+):
     """Write a single ScheduleBlock object in the file like object.
 
     Args:
@@ -610,11 +615,11 @@ def write_schedule_block(
     file_obj.write(block_name)
     file_obj.write(metadata)
 
-    _write_alignment_context(file_obj, block.alignment_context)
+    _write_alignment_context(file_obj, block.alignment_context, version=version)
     for block_elm in block._blocks:
         # Do not call block.blocks. This implicitly assigns references to instruction.
         # This breaks original reference structure.
-        _write_element(file_obj, block_elm, metadata_serializer, use_symengine)
+        _write_element(file_obj, block_elm, metadata_serializer, use_symengine, version=version)
 
     # Write references
     flat_key_refdict = {}
@@ -627,4 +632,5 @@ def write_schedule_block(
         mapping=flat_key_refdict,
         serializer=_dumps_reference_item,
         metadata_serializer=metadata_serializer,
+        version=version,
     )
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
index a3d7ff088139..fdad363867a3 100644
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -53,7 +53,7 @@ def _write_parameter_vec(file_obj, obj):
     file_obj.write(name_bytes)
 
 
-def _write_parameter_expression(file_obj, obj, use_symengine):
+def _write_parameter_expression(file_obj, obj, use_symengine, *, version):
     if use_symengine:
         expr_bytes = obj._symbol_expr.__reduce__()[1][0]
     else:
@@ -81,7 +81,7 @@ def _write_parameter_expression(file_obj, obj, use_symengine):
             value_key = symbol_key
             value_data = bytes()
         else:
-            value_key, value_data = dumps_value(value, use_symengine=use_symengine)
+            value_key, value_data = dumps_value(value, version=version, use_symengine=use_symengine)
 
         elem_header = struct.pack(
             formats.PARAM_EXPR_MAP_ELEM_V3_PACK,
@@ -95,12 +95,13 @@ def _write_parameter_expression(file_obj, obj, use_symengine):
 
 
 class _ExprWriter(expr.ExprVisitor[None]):
-    __slots__ = ("file_obj", "clbit_indices", "standalone_var_indices")
+    __slots__ = ("file_obj", "clbit_indices", "standalone_var_indices", "version")
 
-    def __init__(self, file_obj, clbit_indices, standalone_var_indices):
+    def __init__(self, file_obj, clbit_indices, standalone_var_indices, version):
         self.file_obj = file_obj
         self.clbit_indices = clbit_indices
         self.standalone_var_indices = standalone_var_indices
+        self.version = version
 
     def visit_generic(self, node, /):
         raise exceptions.QpyError(f"unhandled Expr object '{node}'")
@@ -181,19 +182,30 @@ def visit_binary(self, node, /):
         self.file_obj.write(type_keys.Expression.BINARY)
         _write_expr_type(self.file_obj, node.type)
         self.file_obj.write(
-            struct.pack(formats.EXPRESSION_BINARY_PACK, *formats.EXPRESSION_UNARY(node.op.value))
+            struct.pack(formats.EXPRESSION_BINARY_PACK, *formats.EXPRESSION_BINARY(node.op.value))
         )
         node.left.accept(self)
         node.right.accept(self)
 
+    def visit_index(self, node, /):
+        if self.version < 12:
+            raise exceptions.UnsupportedFeatureForVersion(
+                "the 'Index' expression", required=12, target=self.version
+            )
+        self.file_obj.write(type_keys.Expression.INDEX)
+        _write_expr_type(self.file_obj, node.type)
+        node.target.accept(self)
+        node.index.accept(self)
+
 
 def _write_expr(
     file_obj,
     node: expr.Expr,
     clbit_indices: collections.abc.Mapping[Clbit, int],
     standalone_var_indices: collections.abc.Mapping[expr.Var, int],
+    version: int,
 ):
-    node.accept(_ExprWriter(file_obj, clbit_indices, standalone_var_indices))
+    node.accept(_ExprWriter(file_obj, clbit_indices, standalone_var_indices, version))
 
 
 def _write_expr_type(file_obj, type_: types.Type):
@@ -406,7 +418,13 @@ def _read_expr(
             _read_expr(file_obj, clbits, cregs, standalone_vars),
             type_,
         )
-    raise exceptions.QpyError("Invalid classical-expression Expr key '{type_key}'")
+    if type_key == type_keys.Expression.INDEX:
+        return expr.Index(
+            _read_expr(file_obj, clbits, cregs, standalone_vars),
+            _read_expr(file_obj, clbits, cregs, standalone_vars),
+            type_,
+        )
+    raise exceptions.QpyError(f"Invalid classical-expression Expr key '{type_key}'")
 
 
 def _read_expr_type(file_obj) -> types.Type:
@@ -494,11 +512,19 @@ def write_standalone_vars(file_obj, circuit):
     return out
 
 
-def dumps_value(obj, *, index_map=None, use_symengine=False, standalone_var_indices=None):
+def dumps_value(
+    obj,
+    *,
+    version,
+    index_map=None,
+    use_symengine=False,
+    standalone_var_indices=None,
+):
     """Serialize input value object.
 
     Args:
         obj (any): Arbitrary value object to serialize.
+        version (int): the target QPY version for the dump.
         index_map (dict): Dictionary with two keys, "q" and "c".  Each key has a value that is a
             dictionary mapping :class:`.Qubit` or :class:`.Clbit` instances (respectively) to their
             integer indices.
@@ -535,7 +561,7 @@ def dumps_value(obj, *, index_map=None, use_symengine=False, standalone_var_indi
         binary_data = common.data_to_binary(obj, _write_parameter)
     elif type_key == type_keys.Value.PARAMETER_EXPRESSION:
         binary_data = common.data_to_binary(
-            obj, _write_parameter_expression, use_symengine=use_symengine
+            obj, _write_parameter_expression, use_symengine=use_symengine, version=version
         )
     elif type_key == type_keys.Value.EXPRESSION:
         clbit_indices = {} if index_map is None else index_map["c"]
@@ -545,6 +571,7 @@ def dumps_value(obj, *, index_map=None, use_symengine=False, standalone_var_indi
             _write_expr,
             clbit_indices=clbit_indices,
             standalone_var_indices=standalone_var_indices,
+            version=version,
         )
     else:
         raise exceptions.QpyError(f"Serialization for {type_key} is not implemented in value I/O.")
@@ -552,12 +579,15 @@ def dumps_value(obj, *, index_map=None, use_symengine=False, standalone_var_indi
     return type_key, binary_data
 
 
-def write_value(file_obj, obj, *, index_map=None, use_symengine=False, standalone_var_indices=None):
+def write_value(
+    file_obj, obj, *, version, index_map=None, use_symengine=False, standalone_var_indices=None
+):
     """Write a value to the file like object.
 
     Args:
         file_obj (File): A file like object to write data.
         obj (any): Value to write.
+        version (int): the target QPY version for the dump.
         index_map (dict): Dictionary with two keys, "q" and "c".  Each key has a value that is a
             dictionary mapping :class:`.Qubit` or :class:`.Clbit` instances (respectively) to their
             integer indices.
@@ -570,6 +600,7 @@ def write_value(file_obj, obj, *, index_map=None, use_symengine=False, standalon
     """
     type_key, data = dumps_value(
         obj,
+        version=version,
         index_map=index_map,
         use_symengine=use_symengine,
         standalone_var_indices=standalone_var_indices,
diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py
index 6ec85115b559..3ff6b4a35af3 100644
--- a/qiskit/qpy/type_keys.py
+++ b/qiskit/qpy/type_keys.py
@@ -457,6 +457,7 @@ class Expression(TypeKeyBase):
     CAST = b"c"
     UNARY = b"u"
     BINARY = b"b"
+    INDEX = b"i"
 
     @classmethod
     def assign(cls, obj):
diff --git a/releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml b/releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml
new file mode 100644
index 000000000000..78d31f8238a3
--- /dev/null
+++ b/releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml
@@ -0,0 +1,20 @@
+---
+features_circuits:
+  - |
+    The classical realtime-expressions module :mod:`qiskit.circuit.classical` can now represent
+    indexing and bitshifting of unsigned integers and bitlikes (e.g. :class:`.ClassicalRegister`).
+    For example, it is now possible to compare one register with the bitshift of another::
+
+      from qiskit.circuit import QuantumCircuit, ClassicalRegister
+      from qiskit.circuit.classical import expr
+
+      cr1 = ClassicalRegister(4, "cr1")
+      cr2 = ClassicalRegister(4, "cr2")
+      qc = QuantumCircuit(cr1, cr2)
+      with qc.if_test(expr.equal(cr1, expr.shift_left(cr2, 2))):
+          pass
+
+    Qiskit can also represent a condition that dynamically indexes into a register::
+    
+      with qc.if_test(expr.index(cr1, cr2)):
+          pass
diff --git a/test/python/circuit/classical/test_expr_constructors.py b/test/python/circuit/classical/test_expr_constructors.py
index b655acf57496..10cef88122c1 100644
--- a/test/python/circuit/classical/test_expr_constructors.py
+++ b/test/python/circuit/classical/test_expr_constructors.py
@@ -387,3 +387,57 @@ def test_binary_relation_forbidden(self, function):
             function(ClassicalRegister(3, "c"), False)
         with self.assertRaisesRegex(TypeError, "invalid types"):
             function(Clbit(), Clbit())
+
+    def test_index_explicit(self):
+        cr = ClassicalRegister(4, "c")
+        a = expr.Var.new("a", types.Uint(8))
+
+        self.assertEqual(
+            expr.index(cr, 3),
+            expr.Index(expr.Var(cr, types.Uint(4)), expr.Value(3, types.Uint(2)), types.Bool()),
+        )
+        self.assertEqual(
+            expr.index(a, cr),
+            expr.Index(a, expr.Var(cr, types.Uint(4)), types.Bool()),
+        )
+
+    def test_index_forbidden(self):
+        with self.assertRaisesRegex(TypeError, "invalid types"):
+            expr.index(Clbit(), 3)
+        with self.assertRaisesRegex(TypeError, "invalid types"):
+            expr.index(ClassicalRegister(3, "a"), False)
+
+    @ddt.data(
+        (expr.shift_left, expr.Binary.Op.SHIFT_LEFT),
+        (expr.shift_right, expr.Binary.Op.SHIFT_RIGHT),
+    )
+    @ddt.unpack
+    def test_shift_explicit(self, function, opcode):
+        cr = ClassicalRegister(8, "c")
+        a = expr.Var.new("a", types.Uint(4))
+
+        self.assertEqual(
+            function(cr, 5),
+            expr.Binary(
+                opcode, expr.Var(cr, types.Uint(8)), expr.Value(5, types.Uint(3)), types.Uint(8)
+            ),
+        )
+        self.assertEqual(
+            function(a, cr),
+            expr.Binary(opcode, a, expr.Var(cr, types.Uint(8)), types.Uint(4)),
+        )
+        self.assertEqual(
+            function(3, 5, types.Uint(8)),
+            expr.Binary(
+                opcode, expr.Value(3, types.Uint(8)), expr.Value(5, types.Uint(3)), types.Uint(8)
+            ),
+        )
+
+    @ddt.data(expr.shift_left, expr.shift_right)
+    def test_shift_forbidden(self, function):
+        with self.assertRaisesRegex(TypeError, "invalid types"):
+            function(Clbit(), ClassicalRegister(3, "c"))
+        with self.assertRaisesRegex(TypeError, "invalid types"):
+            function(ClassicalRegister(3, "c"), False)
+        with self.assertRaisesRegex(TypeError, "invalid types"):
+            function(Clbit(), Clbit())
diff --git a/test/python/circuit/classical/test_expr_helpers.py b/test/python/circuit/classical/test_expr_helpers.py
index f52a896df469..5264e55a52da 100644
--- a/test/python/circuit/classical/test_expr_helpers.py
+++ b/test/python/circuit/classical/test_expr_helpers.py
@@ -30,6 +30,8 @@ class TestStructurallyEquivalent(QiskitTestCase):
         expr.logic_not(Clbit()),
         expr.bit_and(5, ClassicalRegister(3, "a")),
         expr.logic_and(expr.less(2, ClassicalRegister(3, "a")), expr.lift(Clbit())),
+        expr.shift_left(expr.shift_right(255, 3), 3),
+        expr.index(expr.Var.new("a", types.Uint(8)), 0),
     )
     def test_equivalent_to_self(self, node):
         self.assertTrue(expr.structurally_equivalent(node, node))
@@ -124,6 +126,7 @@ class TestIsLValue(QiskitTestCase):
         expr.Var.new("b", types.Uint(8)),
         expr.Var(Clbit(), types.Bool()),
         expr.Var(ClassicalRegister(8, "cr"), types.Uint(8)),
+        expr.index(expr.Var.new("a", types.Uint(8)), 0),
     )
     def test_happy_cases(self, lvalue):
         self.assertTrue(expr.is_lvalue(lvalue))
@@ -139,6 +142,7 @@ def test_happy_cases(self, lvalue):
             expr.Var.new("b", types.Bool()),
             types.Bool(),
         ),
+        expr.index(expr.bit_not(expr.Var.new("a", types.Uint(8))), 0),
     )
     def test_bad_cases(self, not_an_lvalue):
         self.assertFalse(expr.is_lvalue(not_an_lvalue))
diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py
index 56726b3342d3..6e1dcf77e1ef 100644
--- a/test/python/circuit/classical/test_expr_properties.py
+++ b/test/python/circuit/classical/test_expr_properties.py
@@ -51,6 +51,21 @@ def test_types_can_be_cloned(self, obj):
             expr.Value(True, types.Bool()),
             types.Bool(),
         ),
+        expr.Index(
+            expr.Var.new("a", types.Uint(3)),
+            expr.Binary(
+                expr.Binary.Op.SHIFT_LEFT,
+                expr.Binary(
+                    expr.Binary.Op.SHIFT_RIGHT,
+                    expr.Var.new("b", types.Uint(3)),
+                    expr.Value(1, types.Uint(1)),
+                    types.Uint(3),
+                ),
+                expr.Value(1, types.Uint(1)),
+                types.Uint(3),
+            ),
+            types.Bool(),
+        ),
     )
     def test_expr_can_be_cloned(self, obj):
         """Test that various ways of cloning an `Expr` object are valid and produce equal output."""
diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py
index efae9697f187..216ea3a59bb0 100644
--- a/test/python/circuit/test_circuit_load_from_qpy.py
+++ b/test/python/circuit/test_circuit_load_from_qpy.py
@@ -1916,6 +1916,33 @@ def test_load_empty_vars_switch(self):
             self.assertMinimalVarEqual(old, new)
             self.assertDeprecatedBitProperties(old, new)
 
+    def test_roundtrip_index_expr(self):
+        """Test that the `Index` node round-trips."""
+        a = expr.Var.new("a", types.Uint(8))
+        cr = ClassicalRegister(4, "cr")
+        qc = QuantumCircuit(cr, inputs=[a])
+        qc.store(expr.index(cr, 0), expr.index(a, a))
+        with io.BytesIO() as fptr:
+            dump(qc, fptr)
+            fptr.seek(0)
+            new_qc = load(fptr)[0]
+        self.assertEqual(qc, new_qc)
+        self.assertDeprecatedBitProperties(qc, new_qc)
+
+    def test_roundtrip_bitshift_expr(self):
+        """Test that bit-shift expressions can round-trip."""
+        a = expr.Var.new("a", types.Uint(8))
+        cr = ClassicalRegister(4, "cr")
+        qc = QuantumCircuit(cr, inputs=[a])
+        with qc.if_test(expr.equal(expr.shift_right(expr.shift_left(a, 1), 1), a)):
+            pass
+        with io.BytesIO() as fptr:
+            dump(qc, fptr)
+            fptr.seek(0)
+            new_qc = load(fptr)[0]
+        self.assertEqual(qc, new_qc)
+        self.assertDeprecatedBitProperties(qc, new_qc)
+
     @ddt.idata(range(QPY_COMPATIBILITY_VERSION, 12))
     def test_pre_v12_rejects_standalone_var(self, version):
         """Test that dumping to older QPY versions rejects standalone vars."""
@@ -1926,6 +1953,17 @@ def test_pre_v12_rejects_standalone_var(self, version):
         ):
             dump(qc, fptr, version=version)
 
+    @ddt.idata(range(QPY_COMPATIBILITY_VERSION, 12))
+    def test_pre_v12_rejects_index(self, version):
+        """Test that dumping to older QPY versions rejects the `Index` node."""
+        # Be sure to use a register, since standalone vars would be rejected for other reasons.
+        qc = QuantumCircuit(ClassicalRegister(2, "cr"))
+        qc.store(expr.index(qc.cregs[0], 0), False)
+        with io.BytesIO() as fptr, self.assertRaisesRegex(
+            UnsupportedFeatureForVersion, "version 12 is required.*Index"
+        ):
+            dump(qc, fptr, version=version)
+
 
 class TestSymengineLoadFromQPY(QiskitTestCase):
     """Test use of symengine in qpy set of methods."""
diff --git a/test/python/circuit/test_store.py b/test/python/circuit/test_store.py
index 425eae55a4bf..139192745d2e 100644
--- a/test/python/circuit/test_store.py
+++ b/test/python/circuit/test_store.py
@@ -29,6 +29,14 @@ def test_happy_path_construction(self):
         self.assertEqual(constructed.lvalue, lvalue)
         self.assertEqual(constructed.rvalue, rvalue)
 
+    def test_store_to_index(self):
+        lvalue = expr.index(expr.Var.new("a", types.Uint(8)), 3)
+        rvalue = expr.lift(False)
+        constructed = Store(lvalue, rvalue)
+        self.assertIsInstance(constructed, Store)
+        self.assertEqual(constructed.lvalue, lvalue)
+        self.assertEqual(constructed.rvalue, rvalue)
+
     def test_implicit_cast(self):
         lvalue = expr.Var.new("a", types.Bool())
         rvalue = expr.Var.new("b", types.Uint(8))
@@ -45,6 +53,11 @@ def test_rejects_non_lvalue(self):
         with self.assertRaisesRegex(CircuitError, "not an l-value"):
             Store(not_an_lvalue, rvalue)
 
+        not_an_lvalue = expr.index(expr.shift_right(expr.Var.new("a", types.Uint(8)), 1), 2)
+        rvalue = expr.lift(True)
+        with self.assertRaisesRegex(CircuitError, "not an l-value"):
+            Store(not_an_lvalue, rvalue)
+
     def test_rejects_explicit_cast(self):
         lvalue = expr.Var.new("a", types.Uint(16))
         rvalue = expr.Var.new("b", types.Uint(8))
@@ -122,6 +135,21 @@ def test_allows_stores_with_cregs(self):
         actual = [instruction.operation for instruction in qc.data]
         self.assertEqual(actual, expected)
 
+    def test_allows_stores_with_index(self):
+        cr = ClassicalRegister(8, "cr")
+        a = expr.Var.new("a", types.Uint(3))
+        qc = QuantumCircuit(cr, inputs=[a])
+        qc.store(expr.index(cr, 0), False)
+        qc.store(expr.index(a, 3), True)
+        qc.store(expr.index(cr, a), expr.index(cr, 0))
+        expected = [
+            Store(expr.index(cr, 0), expr.lift(False)),
+            Store(expr.index(a, 3), expr.lift(True)),
+            Store(expr.index(cr, a), expr.index(cr, 0)),
+        ]
+        actual = [instruction.operation for instruction in qc.data]
+        self.assertEqual(actual, expected)
+
     def test_lifts_values(self):
         a = expr.Var.new("a", types.Bool())
         qc = QuantumCircuit(captures=[a])
diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py
index 8589576441a2..598405beaae1 100644
--- a/test/python/qasm3/test_export.py
+++ b/test/python/qasm3/test_export.py
@@ -1585,11 +1585,20 @@ def test_expr_associativity_left(self):
         qc.if_test(expr.equal(expr.bit_and(expr.bit_and(cr1, cr2), cr3), 7), body.copy(), [], [])
         qc.if_test(expr.equal(expr.bit_or(expr.bit_or(cr1, cr2), cr3), 7), body.copy(), [], [])
         qc.if_test(expr.equal(expr.bit_xor(expr.bit_xor(cr1, cr2), cr3), 7), body.copy(), [], [])
+        qc.if_test(
+            expr.equal(expr.shift_left(expr.shift_left(cr1, cr2), cr3), 7), body.copy(), [], []
+        )
+        qc.if_test(
+            expr.equal(expr.shift_right(expr.shift_right(cr1, cr2), cr3), 7), body.copy(), [], []
+        )
+        qc.if_test(
+            expr.equal(expr.shift_left(expr.shift_right(cr1, cr2), cr3), 7), body.copy(), [], []
+        )
         qc.if_test(expr.logic_and(expr.logic_and(cr1[0], cr1[1]), cr1[2]), body.copy(), [], [])
         qc.if_test(expr.logic_or(expr.logic_or(cr1[0], cr1[1]), cr1[2]), body.copy(), [], [])
 
-        # Note that bitwise operations have lower priority than `==` so there's extra parentheses.
-        # All these operators are left-associative in OQ3.
+        # Note that bitwise operations except shift have lower priority than `==` so there's extra
+        # parentheses.  All these operators are left-associative in OQ3.
         expected = """\
 OPENQASM 3.0;
 include "stdgates.inc";
@@ -1602,6 +1611,12 @@ def test_expr_associativity_left(self):
 }
 if ((cr1 ^ cr2 ^ cr3) == 7) {
 }
+if (cr1 << cr2 << cr3 == 7) {
+}
+if (cr1 >> cr2 >> cr3 == 7) {
+}
+if (cr1 >> cr2 << cr3 == 7) {
+}
 if (cr1[0] && cr1[1] && cr1[2]) {
 }
 if (cr1[0] || cr1[1] || cr1[2]) {
@@ -1621,6 +1636,15 @@ def test_expr_associativity_right(self):
         qc.if_test(expr.equal(expr.bit_and(cr1, expr.bit_and(cr2, cr3)), 7), body.copy(), [], [])
         qc.if_test(expr.equal(expr.bit_or(cr1, expr.bit_or(cr2, cr3)), 7), body.copy(), [], [])
         qc.if_test(expr.equal(expr.bit_xor(cr1, expr.bit_xor(cr2, cr3)), 7), body.copy(), [], [])
+        qc.if_test(
+            expr.equal(expr.shift_left(cr1, expr.shift_left(cr2, cr3)), 7), body.copy(), [], []
+        )
+        qc.if_test(
+            expr.equal(expr.shift_right(cr1, expr.shift_right(cr2, cr3)), 7), body.copy(), [], []
+        )
+        qc.if_test(
+            expr.equal(expr.shift_left(cr1, expr.shift_right(cr2, cr3)), 7), body.copy(), [], []
+        )
         qc.if_test(expr.logic_and(cr1[0], expr.logic_and(cr1[1], cr1[2])), body.copy(), [], [])
         qc.if_test(expr.logic_or(cr1[0], expr.logic_or(cr1[1], cr1[2])), body.copy(), [], [])
 
@@ -1640,6 +1664,12 @@ def test_expr_associativity_right(self):
 }
 if ((cr1 ^ (cr2 ^ cr3)) == 7) {
 }
+if (cr1 << (cr2 << cr3) == 7) {
+}
+if (cr1 >> (cr2 >> cr3) == 7) {
+}
+if (cr1 << (cr2 >> cr3) == 7) {
+}
 if (cr1[0] && (cr1[1] && cr1[2])) {
 }
 if (cr1[0] || (cr1[1] || cr1[2])) {
@@ -1709,10 +1739,21 @@ def test_expr_precedence(self):
             ),
         )
 
+        # An extra test of the bitshifting rules, since we have to pick one or the other of
+        # bitshifts vs comparisons due to the typing.  The first operand is inside out, the second
+        bitshifts = expr.equal(
+            expr.shift_left(expr.bit_and(expr.bit_xor(cr, cr), cr), expr.bit_or(cr, cr)),
+            expr.bit_or(
+                expr.bit_xor(expr.shift_right(cr, 3), expr.shift_left(cr, 4)),
+                expr.shift_left(cr, 1),
+            ),
+        )
+
         qc = QuantumCircuit(cr)
         qc.if_test(inside_out, body.copy(), [], [])
         qc.if_test(outside_in, body.copy(), [], [])
         qc.if_test(logics, body.copy(), [], [])
+        qc.if_test(bitshifts, body.copy(), [], [])
 
         expected = """\
 OPENQASM 3.0;
@@ -1726,6 +1767,8 @@ def test_expr_precedence(self):
 }
 if ((!cr[0] || !cr[0]) && !(cr[0] && cr[0]) || !(cr[0] && cr[0]) && (!cr[0] || !cr[0])) {
 }
+if (((cr ^ cr) & cr) << (cr | cr) == (cr >> 3 ^ cr << 4 | cr << 1)) {
+}
 """
         self.assertEqual(dumps(qc), expected)
 
diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py
index 58ee1abc2a2f..3a404672946a 100755
--- a/test/qpy_compat/test_qpy.py
+++ b/test/qpy_compat/test_qpy.py
@@ -802,6 +802,24 @@ def generate_standalone_var():
     return [qc]
 
 
+def generate_v12_expr():
+    """Circuits that contain the `Index` and bitshift operators new in QPY v12."""
+    import uuid
+    from qiskit.circuit.classical import expr, types
+
+    a = expr.Var(uuid.UUID(bytes=b"hello, qpy world", version=4), types.Uint(8), name="a")
+    cr = ClassicalRegister(4, "cr")
+
+    index = QuantumCircuit(cr, inputs=[a], name="index_expr")
+    index.store(expr.index(cr, 0), expr.index(a, a))
+
+    shift = QuantumCircuit(cr, inputs=[a], name="shift_expr")
+    with shift.if_test(expr.equal(expr.shift_right(expr.shift_left(a, 1), 1), a)):
+        pass
+
+    return [index, shift]
+
+
 def generate_circuits(version_parts):
     """Generate reference circuits."""
     output_circuits = {
@@ -852,6 +870,7 @@ def generate_circuits(version_parts):
         output_circuits["annotated.qpy"] = generate_annotated_circuits()
     if version_parts >= (1, 1, 0):
         output_circuits["standalone_vars.qpy"] = generate_standalone_var()
+        output_circuits["v12_expr.qpy"] = generate_v12_expr()
     return output_circuits
 
 

From c53984f10d4d814a62ce35c8b53fac7f632e1e40 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 2 May 2024 09:38:48 -0400
Subject: [PATCH 056/179] Add equivalence library entry for swap to ECR or CZ
 (#12312)

* Add equivalence library entry for swap to ECR or CZ

This commit adds two new equivalence library entries to cover the
conversion from a SWAP gate to either ecr or cz directly. These are
common 2q basis gates and without these entries in the equivalence
library the path found from a lookup ends up with a much less efficient
translation. This commit adds the two new entries so that the
BasisTranslator will use a more efficient decomposition from the start
when targeting these basis. This will hopefully result in less work for
the optimization stage as the output will already be optimal and not
require simplification.

Testing for this PR is handled automatically by the built-in testing
harness in test_gate_definitions.py that evaluates all the entries in
the standard equivalence library for unitary equivalence.

* Add name to annotated gate circuit in qpy backwards compat tests

* Fix equivalence library tests

As fallout from the addition of SingletonGate and
SingletonControlledGate we were accidentally not running large portions
of the unit tests which validate the default session equivalence
library. This test dynamically runs based on all members of the standard
gate library by looking at all defined subclasses of Gate and
ControlledGate. But with the introduction of SingletonGate and
SingletonControlledGate all the unparameterized gates in the library
were not being run through the tests. This commit fixes this to catch
that the swap definition added in the previous commit on this PR branch
used an incorrect definition of SwapGate using ECRGate. The definition
will be fixed in a follow up PR.

* Use a more efficient and actually correct circuit for ECR target

The previous definition of a swap gate using ECR rz and sx was incorrect
and also not as efficient as possible. This was missed because the tests
were accidently broken since #10314 which was fixed in the previous
commit. This commit updates the definition to use one that is actually
correct and also more efficient with fewer 1 qubit gates.

Co-authored-by: Alexander Ivrii 

* Update ECR circuit diagram in comment

* Simplify cz equivalent circuit

* Simplify cz circuit even more

Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Co-authored-by: Alexander Ivrii 

---------

Co-authored-by: Alexander Ivrii 
Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
---
 .../standard_gates/equivalence_library.py     | 50 +++++++++++++++++++
 test/python/circuit/test_gate_definitions.py  | 14 +++++-
 test/qpy_compat/test_qpy.py                   |  2 +-
 3 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py
index 9793dc0202a8..c4619ca27858 100644
--- a/qiskit/circuit/library/standard_gates/equivalence_library.py
+++ b/qiskit/circuit/library/standard_gates/equivalence_library.py
@@ -850,6 +850,56 @@ def _cnot_rxx_decompose(plus_ry: bool = True, plus_rxx: bool = True):
     def_swap.append(inst, qargs, cargs)
 _sel.add_equivalence(SwapGate(), def_swap)
 
+# SwapGate
+#
+# q_0: ─X─
+#       │   ≡
+# q_1: ─X─
+#
+#      ┌──────────┐┌──────┐   ┌────┐   ┌──────┐┌──────────┐┌──────┐
+# q_0: ┤ Rz(-π/2) ├┤0     ├───┤ √X ├───┤1     ├┤ Rz(-π/2) ├┤0     ├
+#      └──┬────┬──┘│  Ecr │┌──┴────┴──┐│  Ecr │└──┬────┬──┘│  Ecr │
+# q_1: ───┤ √X ├───┤1     ├┤ Rz(-π/2) ├┤0     ├───┤ √X ├───┤1     ├
+#         └────┘   └──────┘└──────────┘└──────┘   └────┘   └──────┘
+#
+q = QuantumRegister(2, "q")
+def_swap_ecr = QuantumCircuit(q)
+def_swap_ecr.rz(-pi / 2, 0)
+def_swap_ecr.sx(1)
+def_swap_ecr.ecr(0, 1)
+def_swap_ecr.rz(-pi / 2, 1)
+def_swap_ecr.sx(0)
+def_swap_ecr.ecr(1, 0)
+def_swap_ecr.rz(-pi / 2, 0)
+def_swap_ecr.sx(1)
+def_swap_ecr.ecr(0, 1)
+_sel.add_equivalence(SwapGate(), def_swap_ecr)
+
+# SwapGate
+#
+# q_0: ─X─
+#       │   ≡
+# q_1: ─X─
+#
+# global phase: 3π/2
+#      ┌────┐   ┌────┐   ┌────┐
+# q_0: ┤ √X ├─■─┤ √X ├─■─┤ √X ├─■─
+#      ├────┤ │ ├────┤ │ ├────┤ │
+# q_1: ┤ √X ├─■─┤ √X ├─■─┤ √X ├─■─
+#      └────┘   └────┘   └────┘
+q = QuantumRegister(2, "q")
+def_swap_cz = QuantumCircuit(q, global_phase=-pi / 2)
+def_swap_cz.sx(0)
+def_swap_cz.sx(1)
+def_swap_cz.cz(0, 1)
+def_swap_cz.sx(0)
+def_swap_cz.sx(1)
+def_swap_cz.cz(0, 1)
+def_swap_cz.sx(0)
+def_swap_cz.sx(1)
+def_swap_cz.cz(0, 1)
+_sel.add_equivalence(SwapGate(), def_swap_cz)
+
 # iSwapGate
 #
 #      ┌────────┐          ┌───┐┌───┐     ┌───┐
diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py
index 8e608a163900..38bf7046cae5 100644
--- a/test/python/circuit/test_gate_definitions.py
+++ b/test/python/circuit/test_gate_definitions.py
@@ -21,6 +21,7 @@
 from qiskit import QuantumCircuit, QuantumRegister
 from qiskit.quantum_info import Operator
 from qiskit.circuit import ParameterVector, Gate, ControlledGate
+from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate
 from qiskit.circuit.library import standard_gates
 from qiskit.circuit.library import (
     HGate,
@@ -260,7 +261,12 @@ class TestGateEquivalenceEqual(QiskitTestCase):
     """Test the decomposition of a gate in terms of other gates
     yields the same matrix as the hardcoded matrix definition."""
 
-    class_list = Gate.__subclasses__() + ControlledGate.__subclasses__()
+    class_list = (
+        SingletonGate.__subclasses__()
+        + SingletonControlledGate.__subclasses__()
+        + Gate.__subclasses__()
+        + ControlledGate.__subclasses__()
+    )
     exclude = {
         "ControlledGate",
         "DiagonalGate",
@@ -313,7 +319,11 @@ def test_equivalence_phase(self, gate_class):
             with self.subTest(msg=gate.name + "_" + str(ieq)):
                 op1 = Operator(gate)
                 op2 = Operator(equivalency)
-                self.assertEqual(op1, op2)
+                msg = (
+                    f"Equivalence entry from '{gate.name}' to:\n"
+                    f"{str(equivalency.draw('text'))}\nfailed"
+                )
+                self.assertEqual(op1, op2, msg)
 
 
 @ddt
diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py
index 3a404672946a..f2ce2bee1086 100755
--- a/test/qpy_compat/test_qpy.py
+++ b/test/qpy_compat/test_qpy.py
@@ -661,7 +661,7 @@ def generate_annotated_circuits():
         CXGate(), [InverseModifier(), ControlModifier(1), PowerModifier(1.4), InverseModifier()]
     )
     op2 = AnnotatedOperation(XGate(), InverseModifier())
-    qc = QuantumCircuit(6, 1)
+    qc = QuantumCircuit(6, 1, name="Annotated circuits")
     qc.cx(0, 1)
     qc.append(op1, [0, 1, 2])
     qc.h(4)

From 469c9894d13f142921607cc74b399b70b9282384 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 2 May 2024 15:13:34 +0100
Subject: [PATCH 057/179] Support standalone `Var` throughout transpiler
 (#12322)

* Support standalone `Var` throughout transpiler

This adds the missing pieces to fully support standalone `Var` nodes
through every part of the transpiler (that I could detect).  It's quite
possible there's some problem in a more esoteric non-preset pass
somewhere, but I couldn't spot them.

For the most part there were very few changes needed to the actual
passes, and only one place in `QuantumCircuit` that had previously been
missed.  Most of the commit is updating passes to correctly pass
`inline_captures=True` when appropriate for dealing with
`DAGCircuit.compose`, and making sure that any place that needed to
build a raw `DAGCircuit` for a rebuild _without_ using
`DAGCircuit.copy_empty_like` made sure to correctly add in the
variables.

This commit adds specific tests for every pass that I touched, plus the
general integration tests that we have for the transpiler to make sure
that OQ3 and QPY serialisation work afterwards.

* Clarify comment
---
 qiskit/circuit/quantumcircuit.py              |  4 +-
 qiskit/circuit/store.py                       |  3 +
 .../passes/basis/basis_translator.py          |  4 +-
 .../passes/basis/unroll_custom_definitions.py |  2 +-
 .../transpiler/passes/layout/apply_layout.py  |  6 ++
 .../transpiler/passes/layout/sabre_layout.py  |  6 ++
 .../passes/optimization/optimize_annotated.py |  2 +-
 .../passes/routing/stochastic_swap.py         | 23 +++--
 .../passes/synthesis/high_level_synthesis.py  |  2 +-
 qiskit/transpiler/passes/utils/gates_basis.py |  6 +-
 .../python/circuit/test_circuit_operations.py | 25 +++++
 test/python/compiler/test_transpiler.py       | 89 ++++++++++++++++-
 test/python/transpiler/test_apply_layout.py   | 26 +++++
 .../transpiler/test_basis_translator.py       | 97 ++++++++++++++++++-
 .../transpiler/test_gates_in_basis_pass.py    | 42 ++++++++
 .../transpiler/test_high_level_synthesis.py   | 55 +++++++++++
 .../transpiler/test_optimize_annotated.py     | 24 ++++-
 test/python/transpiler/test_sabre_layout.py   | 43 +++++++-
 .../python/transpiler/test_stochastic_swap.py | 44 ++++++++-
 .../test_unroll_custom_definitions.py         | 56 ++++++++++-
 20 files changed, 536 insertions(+), 23 deletions(-)

diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index ad966b685e71..abd48c686b15 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -621,9 +621,7 @@ def reverse_ops(self) -> "QuantumCircuit":
                 q_1: ┤ RX(1.57) ├─────
                      └──────────┘
         """
-        reverse_circ = QuantumCircuit(
-            self.qubits, self.clbits, *self.qregs, *self.cregs, name=self.name + "_reverse"
-        )
+        reverse_circ = self.copy_empty_like(self.name + "_reverse")
 
         for instruction in reversed(self.data):
             reverse_circ._append(instruction.replace(operation=instruction.operation.reverse_ops()))
diff --git a/qiskit/circuit/store.py b/qiskit/circuit/store.py
index 857cb4f6c2d0..6bbc5439332d 100644
--- a/qiskit/circuit/store.py
+++ b/qiskit/circuit/store.py
@@ -59,6 +59,9 @@ class Store(Instruction):
     :class:`~.circuit.Measure` is a primitive for quantum measurement), and is not safe for
     subclassing."""
 
+    # This is a compiler/backend intrinsic operation, separate to any quantum processing.
+    _directive = True
+
     def __init__(self, lvalue: expr.Expr, rvalue: expr.Expr):
         """
         Args:
diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py
index c38d65817769..074c6d341baa 100644
--- a/qiskit/transpiler/passes/basis/basis_translator.py
+++ b/qiskit/transpiler/passes/basis/basis_translator.py
@@ -148,12 +148,12 @@ def run(self, dag):
 
         # Names of instructions assumed to supported by any backend.
         if self._target is None:
-            basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay"]
+            basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay", "store"]
             target_basis = set(self._target_basis)
             source_basis = set(self._extract_basis(dag))
             qargs_local_source_basis = {}
         else:
-            basic_instrs = ["barrier", "snapshot"]
+            basic_instrs = ["barrier", "snapshot", "store"]
             target_basis = self._target.keys() - set(self._non_global_operations)
             source_basis, qargs_local_source_basis = self._extract_basis_target(dag, qarg_indices)
 
diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
index 12e6811a2f03..2a95f540f886 100644
--- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
+++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
@@ -61,7 +61,7 @@ def run(self, dag):
             return dag
 
         if self._target is None:
-            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
+            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
             device_insts = basic_insts | set(self._basis_gates)
 
         for node in dag.op_nodes():
diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py
index c36a7e111070..e87b6f7ce4e9 100644
--- a/qiskit/transpiler/passes/layout/apply_layout.py
+++ b/qiskit/transpiler/passes/layout/apply_layout.py
@@ -61,6 +61,12 @@ def run(self, dag):
 
         new_dag = DAGCircuit()
         new_dag.add_qreg(q)
+        for var in dag.iter_input_vars():
+            new_dag.add_input_var(var)
+        for var in dag.iter_captured_vars():
+            new_dag.add_captured_var(var)
+        for var in dag.iter_declared_vars():
+            new_dag.add_declared_var(var)
         new_dag.metadata = dag.metadata
         new_dag.add_clbits(dag.clbits)
         for creg in dag.cregs.values():
diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py
index 92227f3c37d6..31609b878683 100644
--- a/qiskit/transpiler/passes/layout/sabre_layout.py
+++ b/qiskit/transpiler/passes/layout/sabre_layout.py
@@ -308,6 +308,12 @@ def run(self, dag):
         mapped_dag.add_clbits(dag.clbits)
         for creg in dag.cregs.values():
             mapped_dag.add_creg(creg)
+        for var in dag.iter_input_vars():
+            mapped_dag.add_input_var(var)
+        for var in dag.iter_captured_vars():
+            mapped_dag.add_captured_var(var)
+        for var in dag.iter_declared_vars():
+            mapped_dag.add_declared_var(var)
         mapped_dag._global_phase = dag._global_phase
         self.property_set["original_qubit_indices"] = {
             bit: index for index, bit in enumerate(dag.qubits)
diff --git a/qiskit/transpiler/passes/optimization/optimize_annotated.py b/qiskit/transpiler/passes/optimization/optimize_annotated.py
index 65d06436cc5c..0b9b786a07f4 100644
--- a/qiskit/transpiler/passes/optimization/optimize_annotated.py
+++ b/qiskit/transpiler/passes/optimization/optimize_annotated.py
@@ -77,7 +77,7 @@ def __init__(
         self._top_level_only = not recurse or (self._basis_gates is None and self._target is None)
 
         if not self._top_level_only and self._target is None:
-            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
+            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
             self._device_insts = basic_insts | set(self._basis_gates)
 
     def run(self, dag: DAGCircuit):
diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py
index 3b80bf7b31af..ec7ea8149137 100644
--- a/qiskit/transpiler/passes/routing/stochastic_swap.py
+++ b/qiskit/transpiler/passes/routing/stochastic_swap.py
@@ -33,7 +33,6 @@
     ForLoopOp,
     SwitchCaseOp,
     ControlFlowOp,
-    Instruction,
     CASE_DEFAULT,
 )
 from qiskit._accelerate import stochastic_swap as stochastic_swap_rs
@@ -266,11 +265,15 @@ def _layer_update(self, dag, layer, best_layout, best_depth, best_circuit):
         # Output any swaps
         if best_depth > 0:
             logger.debug("layer_update: there are swaps in this layer, depth %d", best_depth)
-            dag.compose(best_circuit, qubits={bit: bit for bit in best_circuit.qubits})
+            dag.compose(
+                best_circuit, qubits={bit: bit for bit in best_circuit.qubits}, inline_captures=True
+            )
         else:
             logger.debug("layer_update: there are no swaps in this layer")
         # Output this layer
-        dag.compose(layer["graph"], qubits=best_layout.reorder_bits(dag.qubits))
+        dag.compose(
+            layer["graph"], qubits=best_layout.reorder_bits(dag.qubits), inline_captures=True
+        )
 
     def _mapper(self, circuit_graph, coupling_graph, trials=20):
         """Map a DAGCircuit onto a CouplingMap using swap gates.
@@ -438,7 +441,7 @@ def _controlflow_layer_update(self, dagcircuit_output, layer_dag, current_layout
                 root_dag, self.coupling_map, layout, final_layout, seed=self._new_seed()
             )
             if swap_dag.size(recurse=False):
-                updated_dag_block.compose(swap_dag, qubits=swap_qubits)
+                updated_dag_block.compose(swap_dag, qubits=swap_qubits, inline_captures=True)
             idle_qubits &= set(updated_dag_block.idle_wires())
 
         # Now for each block, expand it to be full width over all active wires (all blocks of a
@@ -504,10 +507,18 @@ def _dag_from_block(block, node, root_dag):
         out.add_qreg(qreg)
     # For clbits, we need to take more care.  Nested control-flow might need registers to exist for
     # conditions on inner blocks.  `DAGCircuit.substitute_node_with_dag` handles this register
-    # mapping when required, so we use that with a dummy block.
+    # mapping when required, so we use that with a dummy block that pretends to act on all variables
+    # in the DAG.
     out.add_clbits(node.cargs)
+    for var in block.iter_input_vars():
+        out.add_input_var(var)
+    for var in block.iter_captured_vars():
+        out.add_captured_var(var)
+    for var in block.iter_declared_vars():
+        out.add_declared_var(var)
+
     dummy = out.apply_operation_back(
-        Instruction("dummy", len(node.qargs), len(node.cargs), []),
+        IfElseOp(expr.lift(True), block.copy_empty_like(vars_mode="captures")),
         node.qargs,
         node.cargs,
         check=False,
diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
index fd21ae6a75fc..150874a84c7d 100644
--- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
@@ -363,7 +363,7 @@ def __init__(
 
         # include path for when target exists but target.num_qubits is None (BasicSimulator)
         if not self._top_level_only and (self._target is None or self._target.num_qubits is None):
-            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
+            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
             self._device_insts = basic_insts | set(self._basis_gates)
 
     def run(self, dag: DAGCircuit) -> DAGCircuit:
diff --git a/qiskit/transpiler/passes/utils/gates_basis.py b/qiskit/transpiler/passes/utils/gates_basis.py
index 657b1d134852..b1f004cc0df3 100644
--- a/qiskit/transpiler/passes/utils/gates_basis.py
+++ b/qiskit/transpiler/passes/utils/gates_basis.py
@@ -32,7 +32,7 @@ def __init__(self, basis_gates=None, target=None):
         self._basis_gates = None
         if basis_gates is not None:
             self._basis_gates = set(basis_gates).union(
-                {"measure", "reset", "barrier", "snapshot", "delay"}
+                {"measure", "reset", "barrier", "snapshot", "delay", "store"}
             )
         self._target = target
 
@@ -46,8 +46,8 @@ def run(self, dag):
 
             def _visit_target(dag, wire_map):
                 for gate in dag.op_nodes():
-                    # Barrier is universal and supported by all backends
-                    if gate.name == "barrier":
+                    # Barrier and store are assumed universal and supported by all backends
+                    if gate.name in ("barrier", "store"):
                         continue
                     if not self._target.instruction_supported(
                         gate.name, tuple(wire_map[bit] for bit in gate.qargs)
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index 9a934d70c710..6caf194d37d9 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -1002,6 +1002,31 @@ def test_reverse(self):
 
         self.assertEqual(qc.reverse_ops(), expected)
 
+    def test_reverse_with_standlone_vars(self):
+        """Test that instruction-reversing works in the presence of stand-alone variables."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Uint(8))
+
+        qc = QuantumCircuit(2, inputs=[a])
+        qc.add_var(b, 12)
+        qc.h(0)
+        qc.cx(0, 1)
+        with qc.if_test(a):
+            # We don't really comment on what should happen within control-flow operations in this
+            # method - it's not really defined in a non-linear CFG.  This deliberately uses a body
+            # of length 1 (a single `Store`), so there's only one possibility.
+            qc.add_var(c, 12)
+
+        expected = qc.copy_empty_like()
+        with expected.if_test(a):
+            expected.add_var(c, 12)
+        expected.cx(0, 1)
+        expected.h(0)
+        expected.store(b, 12)
+
+        self.assertEqual(qc.reverse_ops(), expected)
+
     def test_repeat(self):
         """Test repeating the circuit works."""
         qr = QuantumRegister(2)
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
index 943de7b932e7..2a8c86b27ab0 100644
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -42,13 +42,13 @@
     SwitchCaseOp,
     WhileLoopOp,
 )
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.annotated_operation import (
     AnnotatedOperation,
     InverseModifier,
     ControlModifier,
     PowerModifier,
 )
-from qiskit.circuit.classical import expr
 from qiskit.circuit.delay import Delay
 from qiskit.circuit.measure import Measure
 from qiskit.circuit.reset import Reset
@@ -2175,6 +2175,38 @@ def _control_flow_expr_circuit(self):
                 base.append(CustomCX(), [3, 4])
         return base
 
+    def _standalone_var_circuit(self):
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Uint(8))
+
+        qc = QuantumCircuit(5, 5, inputs=[a])
+        qc.add_var(b, 12)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        qc.store(a, expr.bit_xor(qc.clbits[0], qc.clbits[1]))
+        with qc.if_test(a) as else_:
+            qc.cx(2, 3)
+            qc.cx(3, 4)
+            qc.cx(4, 2)
+        with else_:
+            qc.add_var(c, 12)
+        with qc.while_loop(a):
+            with qc.while_loop(a):
+                qc.add_var(c, 12)
+                qc.cz(1, 0)
+                qc.cz(4, 1)
+                qc.store(a, False)
+        with qc.switch(expr.bit_and(b, 7)) as case:
+            with case(0):
+                qc.cz(0, 1)
+                qc.cx(1, 2)
+                qc.cy(2, 0)
+            with case(case.DEFAULT):
+                qc.store(b, expr.bit_and(b, 7))
+        return qc
+
     @data(0, 1, 2, 3)
     def test_qpy_roundtrip(self, optimization_level):
         """Test that the output of a transpiled circuit can be round-tripped through QPY."""
@@ -2300,6 +2332,46 @@ def test_qpy_roundtrip_control_flow_expr_backendv2(self, optimization_level):
         round_tripped = qpy.load(buffer)[0]
         self.assertEqual(round_tripped, transpiled)
 
+    @data(0, 1, 2, 3)
+    def test_qpy_roundtrip_standalone_var(self, optimization_level):
+        """Test that the output of a transpiled circuit with control flow including standalone `Var`
+        nodes can be round-tripped through QPY."""
+        backend = GenericBackendV2(num_qubits=7)
+        transpiled = transpile(
+            self._standalone_var_circuit(),
+            backend=backend,
+            basis_gates=backend.operation_names
+            + ["if_else", "for_loop", "while_loop", "switch_case"],
+            optimization_level=optimization_level,
+            seed_transpiler=2024_05_01,
+        )
+        buffer = io.BytesIO()
+        qpy.dump(transpiled, buffer)
+        buffer.seek(0)
+        round_tripped = qpy.load(buffer)[0]
+        self.assertEqual(round_tripped, transpiled)
+
+    @data(0, 1, 2, 3)
+    def test_qpy_roundtrip_standalone_var_target(self, optimization_level):
+        """Test that the output of a transpiled circuit with control flow including standalone `Var`
+        nodes can be round-tripped through QPY."""
+        backend = GenericBackendV2(num_qubits=11)
+        backend.target.add_instruction(IfElseOp, name="if_else")
+        backend.target.add_instruction(ForLoopOp, name="for_loop")
+        backend.target.add_instruction(WhileLoopOp, name="while_loop")
+        backend.target.add_instruction(SwitchCaseOp, name="switch_case")
+        transpiled = transpile(
+            self._standalone_var_circuit(),
+            backend=backend,
+            optimization_level=optimization_level,
+            seed_transpiler=2024_05_01,
+        )
+        buffer = io.BytesIO()
+        qpy.dump(transpiled, buffer)
+        buffer.seek(0)
+        round_tripped = qpy.load(buffer)[0]
+        self.assertEqual(round_tripped, transpiled)
+
     @data(0, 1, 2, 3)
     def test_qasm3_output(self, optimization_level):
         """Test that the output of a transpiled circuit can be dumped into OpenQASM 3."""
@@ -2350,6 +2422,21 @@ def test_qasm3_output_control_flow_expr(self, optimization_level):
             str,
         )
 
+    @data(0, 1, 2, 3)
+    def test_qasm3_output_standalone_var(self, optimization_level):
+        """Test that the output of a transpiled circuit with control flow and standalone `Var` nodes
+        can be dumped into OpenQASM 3."""
+        transpiled = transpile(
+            self._standalone_var_circuit(),
+            backend=GenericBackendV2(num_qubits=13, control_flow=True),
+            optimization_level=optimization_level,
+            seed_transpiler=2024_05_01,
+        )
+        # TODO: There's not a huge amount we can sensibly test for the output here until we can
+        # round-trip the OpenQASM 3 back into a Terra circuit.  Mostly we're concerned that the dump
+        # itself doesn't throw an error, though.
+        self.assertIsInstance(qasm3.dumps(transpiled), str)
+
     @data(0, 1, 2, 3)
     def test_transpile_target_no_measurement_error(self, opt_level):
         """Test that transpile with a target which contains ideal measurement works
diff --git a/test/python/transpiler/test_apply_layout.py b/test/python/transpiler/test_apply_layout.py
index bd119c010f04..b92cc710095d 100644
--- a/test/python/transpiler/test_apply_layout.py
+++ b/test/python/transpiler/test_apply_layout.py
@@ -15,6 +15,7 @@
 import unittest
 
 from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister
+from qiskit.circuit.classical import expr, types
 from qiskit.converters import circuit_to_dag
 from qiskit.transpiler.layout import Layout
 from qiskit.transpiler.passes import ApplyLayout, SetLayout
@@ -167,6 +168,31 @@ def test_final_layout_is_updated(self):
             ),
         )
 
+    def test_works_with_var_nodes(self):
+        """Test that standalone var nodes work."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        qc.add_var(b, 12)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        qc.store(a, expr.bit_and(a, expr.bit_xor(qc.clbits[0], qc.clbits[1])))
+
+        expected = QuantumCircuit(QuantumRegister(2, "q"), *qc.cregs, inputs=[a])
+        expected.add_var(b, 12)
+        expected.h(1)
+        expected.cx(1, 0)
+        expected.measure([1, 0], [0, 1])
+        expected.store(a, expr.bit_and(a, expr.bit_xor(qc.clbits[0], qc.clbits[1])))
+
+        pass_ = ApplyLayout()
+        pass_.property_set["layout"] = Layout(dict(enumerate(reversed(qc.qubits))))
+        after = pass_(qc)
+
+        self.assertEqual(after, expected)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py
index 218cd8162d50..24e5e68ba987 100644
--- a/test/python/transpiler/test_basis_translator.py
+++ b/test/python/transpiler/test_basis_translator.py
@@ -19,8 +19,10 @@
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit import transpile
-from qiskit.circuit import Gate, Parameter, EquivalenceLibrary, Qubit, Clbit
+from qiskit.circuit import Gate, Parameter, EquivalenceLibrary, Qubit, Clbit, Measure
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import (
+    HGate,
     U1Gate,
     U2Gate,
     U3Gate,
@@ -889,6 +891,50 @@ def test_unrolling_parameterized_composite_gates(self):
 
         self.assertEqual(circuit_to_dag(expected), out_dag)
 
+    def test_treats_store_as_builtin(self):
+        """Test that the `store` instruction is allowed as a builtin in all cases with no target."""
+
+        class MyHGate(Gate):
+            """Hadamard, but it's _mine_."""
+
+            def __init__(self):
+                super().__init__("my_h", 1, [])
+
+        class MyCXGate(Gate):
+            """CX, but it's _mine_."""
+
+            def __init__(self):
+                super().__init__("my_cx", 2, [])
+
+        h_to_my = QuantumCircuit(1)
+        h_to_my.append(MyHGate(), [0], [])
+        cx_to_my = QuantumCircuit(2)
+        cx_to_my.append(MyCXGate(), [0, 1], [])
+        eq_lib = EquivalenceLibrary()
+        eq_lib.add_equivalence(HGate(), h_to_my)
+        eq_lib.add_equivalence(CXGate(), cx_to_my)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        qc.add_var(b, 12)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        qc.store(a, expr.bit_xor(qc.clbits[0], qc.clbits[1]))
+
+        expected = qc.copy_empty_like()
+        expected.store(b, 12)
+        expected.append(MyHGate(), [0], [])
+        expected.append(MyCXGate(), [0, 1], [])
+        expected.measure([0, 1], [0, 1])
+        expected.store(a, expr.bit_xor(expected.clbits[0], expected.clbits[1]))
+
+        # Note: store is present in the circuit but not in the basis set.
+        out = BasisTranslator(eq_lib, ["my_h", "my_cx"])(qc)
+        self.assertEqual(out, expected)
+
 
 class TestBasisExamples(QiskitTestCase):
     """Test example circuits targeting example bases over the StandardEquivalenceLibrary."""
@@ -1127,3 +1173,52 @@ def test_2q_with_non_global_1q(self):
         expected.sx(1)
         expected.rz(3 * pi, 1)
         self.assertEqual(output, expected)
+
+    def test_treats_store_as_builtin(self):
+        """Test that the `store` instruction is allowed as a builtin in all cases with a target."""
+
+        class MyHGate(Gate):
+            """Hadamard, but it's _mine_."""
+
+            def __init__(self):
+                super().__init__("my_h", 1, [])
+
+        class MyCXGate(Gate):
+            """CX, but it's _mine_."""
+
+            def __init__(self):
+                super().__init__("my_cx", 2, [])
+
+        h_to_my = QuantumCircuit(1)
+        h_to_my.append(MyHGate(), [0], [])
+        cx_to_my = QuantumCircuit(2)
+        cx_to_my.append(MyCXGate(), [0, 1], [])
+        eq_lib = EquivalenceLibrary()
+        eq_lib.add_equivalence(HGate(), h_to_my)
+        eq_lib.add_equivalence(CXGate(), cx_to_my)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(2, 2, inputs=[a])
+        qc.add_var(b, 12)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        qc.store(a, expr.bit_xor(qc.clbits[0], qc.clbits[1]))
+
+        expected = qc.copy_empty_like()
+        expected.store(b, 12)
+        expected.append(MyHGate(), [0], [])
+        expected.append(MyCXGate(), [0, 1], [])
+        expected.measure([0, 1], [0, 1])
+        expected.store(a, expr.bit_xor(expected.clbits[0], expected.clbits[1]))
+
+        # Note: store is present in the circuit but not in the target.
+        target = Target()
+        target.add_instruction(MyHGate(), {(i,): None for i in range(qc.num_qubits)})
+        target.add_instruction(Measure(), {(i,): None for i in range(qc.num_qubits)})
+        target.add_instruction(MyCXGate(), {(0, 1): None, (1, 0): None})
+
+        out = BasisTranslator(eq_lib, {"my_h", "my_cx"}, target)(qc)
+        self.assertEqual(out, expected)
diff --git a/test/python/transpiler/test_gates_in_basis_pass.py b/test/python/transpiler/test_gates_in_basis_pass.py
index 2138070ed9d9..06ce5e0f6702 100644
--- a/test/python/transpiler/test_gates_in_basis_pass.py
+++ b/test/python/transpiler/test_gates_in_basis_pass.py
@@ -13,6 +13,7 @@
 """Test GatesInBasis pass."""
 
 from qiskit.circuit import QuantumCircuit, ForLoopOp, IfElseOp, SwitchCaseOp, Clbit
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import HGate, CXGate, UGate, XGate, ZGate
 from qiskit.circuit.measure import Measure
 from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
@@ -269,3 +270,44 @@ def test_basis_gates_target(self):
         pass_ = GatesInBasis(target=complete)
         pass_(circuit)
         self.assertTrue(pass_.property_set["all_gates_in_basis"])
+
+    def test_store_is_treated_as_builtin_basis_gates(self):
+        """Test that `Store` is treated as an automatic built-in when given basis gates."""
+        pass_ = GatesInBasis(basis_gates=["h", "cx"])
+
+        a = expr.Var.new("a", types.Bool())
+        good = QuantumCircuit(2, inputs=[a])
+        good.store(a, False)
+        good.h(0)
+        good.cx(0, 1)
+        _ = pass_(good)
+        self.assertTrue(pass_.property_set["all_gates_in_basis"])
+
+        bad = QuantumCircuit(2, inputs=[a])
+        bad.store(a, False)
+        bad.x(0)
+        bad.cz(0, 1)
+        _ = pass_(bad)
+        self.assertFalse(pass_.property_set["all_gates_in_basis"])
+
+    def test_store_is_treated_as_builtin_target(self):
+        """Test that `Store` is treated as an automatic built-in when given a target."""
+        target = Target()
+        target.add_instruction(HGate(), {(0,): None, (1,): None})
+        target.add_instruction(CXGate(), {(0, 1): None, (1, 0): None})
+        pass_ = GatesInBasis(target=target)
+
+        a = expr.Var.new("a", types.Bool())
+        good = QuantumCircuit(2, inputs=[a])
+        good.store(a, False)
+        good.h(0)
+        good.cx(0, 1)
+        _ = pass_(good)
+        self.assertTrue(pass_.property_set["all_gates_in_basis"])
+
+        bad = QuantumCircuit(2, inputs=[a])
+        bad.store(a, False)
+        bad.x(0)
+        bad.cz(0, 1)
+        _ = pass_(bad)
+        self.assertFalse(pass_.property_set["all_gates_in_basis"])
diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py
index 0f074865f419..9a2432b82f9c 100644
--- a/test/python/transpiler/test_high_level_synthesis.py
+++ b/test/python/transpiler/test_high_level_synthesis.py
@@ -28,6 +28,7 @@
     Operation,
     EquivalenceLibrary,
 )
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import (
     SwapGate,
     CXGate,
@@ -36,6 +37,7 @@
     U3Gate,
     U2Gate,
     U1Gate,
+    UGate,
     CU3Gate,
     CU1Gate,
 )
@@ -2042,6 +2044,59 @@ def test_unroll_empty_definition_with_phase(self):
         expected = QuantumCircuit(2, global_phase=0.5)
         self.assertEqual(pass_(qc), expected)
 
+    def test_leave_store_alone_basis(self):
+        """Don't attempt to synthesise `Store` instructions with basis gates."""
+
+        pass_ = HighLevelSynthesis(equivalence_library=std_eqlib, basis_gates=["u", "cx"])
+
+        bell = QuantumCircuit(2)
+        bell.h(0)
+        bell.cx(0, 1)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        qc = QuantumCircuit(2, inputs=[a])
+        qc.add_var(b, a)
+        qc.compose(bell, [0, 1], inplace=True)
+        qc.store(b, a)
+
+        expected = qc.copy_empty_like()
+        expected.store(b, a)
+        expected.compose(pass_(bell), [0, 1], inplace=True)
+        expected.store(b, a)
+
+        self.assertEqual(pass_(qc), expected)
+
+    def test_leave_store_alone_with_target(self):
+        """Don't attempt to synthesise `Store` instructions with a `Target`."""
+
+        # Note no store.
+        target = Target()
+        target.add_instruction(
+            UGate(Parameter("a"), Parameter("b"), Parameter("c")), {(0,): None, (1,): None}
+        )
+        target.add_instruction(CXGate(), {(0, 1): None, (1, 0): None})
+
+        pass_ = HighLevelSynthesis(equivalence_library=std_eqlib, target=target)
+
+        bell = QuantumCircuit(2)
+        bell.h(0)
+        bell.cx(0, 1)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        qc = QuantumCircuit(2, inputs=[a])
+        qc.add_var(b, a)
+        qc.compose(bell, [0, 1], inplace=True)
+        qc.store(b, a)
+
+        expected = qc.copy_empty_like()
+        expected.store(b, a)
+        expected.compose(pass_(bell), [0, 1], inplace=True)
+        expected.store(b, a)
+
+        self.assertEqual(pass_(qc), expected)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/python/transpiler/test_optimize_annotated.py b/test/python/transpiler/test_optimize_annotated.py
index 6a506516d199..5e573b551dd5 100644
--- a/test/python/transpiler/test_optimize_annotated.py
+++ b/test/python/transpiler/test_optimize_annotated.py
@@ -13,7 +13,8 @@
 """Test OptimizeAnnotated pass"""
 
 from qiskit.circuit import QuantumCircuit, Gate
-from qiskit.circuit.library import SwapGate, CXGate
+from qiskit.circuit.classical import expr, types
+from qiskit.circuit.library import SwapGate, CXGate, HGate
 from qiskit.circuit.annotated_operation import (
     AnnotatedOperation,
     ControlModifier,
@@ -193,3 +194,24 @@ def test_if_else(self):
         )
 
         self.assertEqual(qc_optimized, expected_qc)
+
+    def test_standalone_var(self):
+        """Test that standalone vars work."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(3, 3, inputs=[a])
+        qc.add_var(b, 12)
+        qc.append(AnnotatedOperation(HGate(), [ControlModifier(1), ControlModifier(1)]), [0, 1, 2])
+        qc.append(AnnotatedOperation(CXGate(), [InverseModifier(), InverseModifier()]), [0, 1])
+        qc.measure([0, 1, 2], [0, 1, 2])
+        qc.store(a, expr.logic_and(qc.clbits[0], qc.clbits[1]))
+
+        expected = qc.copy_empty_like()
+        expected.store(b, 12)
+        expected.append(HGate().control(2, annotated=True), [0, 1, 2])
+        expected.cx(0, 1)
+        expected.measure([0, 1, 2], [0, 1, 2])
+        expected.store(a, expr.logic_and(expected.clbits[0], expected.clbits[1]))
+
+        self.assertEqual(OptimizeAnnotated()(qc), expected)
diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py
index 7640149e039e..487fbf9daef1 100644
--- a/test/python/transpiler/test_sabre_layout.py
+++ b/test/python/transpiler/test_sabre_layout.py
@@ -17,9 +17,10 @@
 import math
 
 from qiskit import QuantumRegister, QuantumCircuit
+from qiskit.circuit.classical import expr, types
 from qiskit.circuit.library import EfficientSU2
 from qiskit.transpiler import CouplingMap, AnalysisPass, PassManager
-from qiskit.transpiler.passes import SabreLayout, DenseLayout
+from qiskit.transpiler.passes import SabreLayout, DenseLayout, StochasticSwap
 from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.converters import circuit_to_dag
 from qiskit.compiler.transpiler import transpile
@@ -257,6 +258,46 @@ def test_layout_many_search_trials(self):
             [layout[q] for q in qc.qubits], [22, 7, 2, 12, 1, 5, 14, 4, 11, 0, 16, 15, 3, 10]
         )
 
+    def test_support_var_with_rust_fastpath(self):
+        """Test that the joint layout/embed/routing logic for the Rust-space fast-path works in the
+        presence of standalone `Var` nodes."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(5, inputs=[a])
+        qc.add_var(b, 12)
+        qc.cx(0, 1)
+        qc.cx(1, 2)
+        qc.cx(2, 3)
+        qc.cx(3, 4)
+        qc.cx(4, 0)
+
+        out = SabreLayout(CouplingMap.from_line(8), seed=0, swap_trials=2, layout_trials=2)(qc)
+
+        self.assertIsInstance(out, QuantumCircuit)
+        self.assertEqual(out.layout.initial_index_layout(), [4, 5, 6, 3, 2, 0, 1, 7])
+
+    def test_support_var_with_explicit_routing_pass(self):
+        """Test that the logic works if an explicit routing pass is given."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+
+        qc = QuantumCircuit(5, inputs=[a])
+        qc.add_var(b, 12)
+        qc.cx(0, 1)
+        qc.cx(1, 2)
+        qc.cx(2, 3)
+        qc.cx(3, 4)
+        qc.cx(4, 0)
+
+        cm = CouplingMap.from_line(8)
+        pass_ = SabreLayout(
+            cm, seed=0, routing_pass=StochasticSwap(cm, trials=1, seed=0, fake_run=True)
+        )
+        _ = pass_(qc)
+        layout = pass_.property_set["layout"]
+        self.assertEqual([layout[q] for q in qc.qubits], [2, 3, 4, 1, 5])
+
 
 class DensePartialSabreTrial(AnalysisPass):
     """Pass to run dense layout as a sabre trial."""
diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py
index fb27076d03de..df5948ed715b 100644
--- a/test/python/transpiler/test_stochastic_swap.py
+++ b/test/python/transpiler/test_stochastic_swap.py
@@ -27,7 +27,7 @@
 from qiskit.providers.fake_provider import Fake27QPulseV1, GenericBackendV2
 from qiskit.compiler.transpiler import transpile
 from qiskit.circuit import ControlFlowOp, Clbit, CASE_DEFAULT
-from qiskit.circuit.classical import expr
+from qiskit.circuit.classical import expr, types
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 from test.utils._canonical import canonicalize_control_flow  # pylint: disable=wrong-import-order
 
@@ -897,6 +897,48 @@ def test_if_else_expr(self):
         check_map_pass.run(cdag)
         self.assertTrue(check_map_pass.property_set["is_swap_mapped"])
 
+    def test_standalone_vars(self):
+        """Test that the routing works in the presence of stand-alone variables."""
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Uint(8))
+        c = expr.Var.new("c", types.Uint(8))
+        qc = QuantumCircuit(5, inputs=[a])
+        qc.add_var(b, 12)
+        qc.cx(0, 2)
+        qc.cx(1, 3)
+        qc.cx(3, 2)
+        qc.cx(3, 0)
+        qc.cx(4, 2)
+        qc.cx(4, 0)
+        qc.cx(1, 4)
+        qc.cx(3, 4)
+        with qc.if_test(a):
+            qc.store(a, False)
+            qc.add_var(c, 12)
+            qc.cx(0, 1)
+        with qc.if_test(a) as else_:
+            qc.store(a, False)
+            qc.add_var(c, 12)
+            qc.cx(0, 1)
+        with else_:
+            qc.cx(1, 2)
+        with qc.while_loop(a):
+            with qc.while_loop(a):
+                qc.add_var(c, 12)
+                qc.cx(1, 3)
+                qc.store(a, False)
+        with qc.switch(b) as case:
+            with case(0):
+                qc.add_var(c, 12)
+                qc.cx(3, 1)
+            with case(case.DEFAULT):
+                qc.cx(3, 1)
+
+        cm = CouplingMap.from_line(5)
+        pm = PassManager([StochasticSwap(cm, seed=0), CheckMap(cm)])
+        _ = pm.run(qc)
+        self.assertTrue(pm.property_set["is_swap_mapped"])
+
     def test_no_layout_change(self):
         """test controlflow with no layout change needed"""
         num_qubits = 5
diff --git a/test/python/transpiler/test_unroll_custom_definitions.py b/test/python/transpiler/test_unroll_custom_definitions.py
index cfed023795d7..5bd16f027e45 100644
--- a/test/python/transpiler/test_unroll_custom_definitions.py
+++ b/test/python/transpiler/test_unroll_custom_definitions.py
@@ -16,10 +16,11 @@
 
 from qiskit.circuit import EquivalenceLibrary, Gate, Qubit, Clbit, Parameter
 from qiskit.circuit import QuantumCircuit, QuantumRegister
+from qiskit.circuit.classical import expr, types
 from qiskit.converters import circuit_to_dag
 from qiskit.exceptions import QiskitError
 from qiskit.transpiler import Target
-from qiskit.circuit.library import CXGate, U3Gate
+from qiskit.circuit.library import CXGate, U3Gate, UGate
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -317,3 +318,56 @@ def test_unroll_empty_definition_with_phase(self):
         pass_ = UnrollCustomDefinitions(EquivalenceLibrary(), ["u"])
         expected = QuantumCircuit(2, global_phase=0.5)
         self.assertEqual(pass_(qc), expected)
+
+    def test_leave_store_alone(self):
+        """Don't attempt to unroll `Store` instructions."""
+
+        pass_ = UnrollCustomDefinitions(EquivalenceLibrary(), ["u", "cx"])
+
+        bell = QuantumCircuit(2)
+        bell.h(0)
+        bell.cx(0, 1)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        qc = QuantumCircuit(2, inputs=[a])
+        qc.add_var(b, a)
+        qc.compose(bell, [0, 1], inplace=True)
+        qc.store(b, a)
+
+        expected = qc.copy_empty_like()
+        expected.store(b, a)
+        expected.compose(pass_(bell), [0, 1], inplace=True)
+        expected.store(b, a)
+
+        self.assertEqual(pass_(qc), expected)
+
+    def test_leave_store_alone_with_target(self):
+        """Don't attempt to unroll `Store` instructions with a `Target`."""
+
+        # Note no store.
+        target = Target()
+        target.add_instruction(
+            UGate(Parameter("a"), Parameter("b"), Parameter("c")), {(0,): None, (1,): None}
+        )
+        target.add_instruction(CXGate(), {(0, 1): None, (1, 0): None})
+
+        pass_ = UnrollCustomDefinitions(EquivalenceLibrary(), target=target)
+
+        bell = QuantumCircuit(2)
+        bell.h(0)
+        bell.cx(0, 1)
+
+        a = expr.Var.new("a", types.Bool())
+        b = expr.Var.new("b", types.Bool())
+        qc = QuantumCircuit(2, inputs=[a])
+        qc.add_var(b, a)
+        qc.compose(bell, [0, 1], inplace=True)
+        qc.store(b, a)
+
+        expected = qc.copy_empty_like()
+        expected.store(b, a)
+        expected.compose(pass_(bell), [0, 1], inplace=True)
+        expected.store(b, a)
+
+        self.assertEqual(pass_(qc), expected)

From 122c64e758caa8b1febacce2f41a3a9e1684adc5 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 2 May 2024 10:13:48 -0400
Subject: [PATCH 058/179] Add ElidePermutations transpiler pass (#9523)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add ElideSwaps transpiler pass

This commit adds a new transpiler pass ElideSwaps which is a logical
optimization pass designed to run prior to layout or any other physical
embedding steps in the transpiler pipeline. It traverses the DAG in
topological order and when a swap gate is encountered it removes that
gate and instead permutes the logical qubits for any subsequent gates
in the DAG. This will eliminate any swaps in a circuit not caused by
routing. Additionally, this pass is added to the preset pass manager for
optimization level 3, we can consider adding it to other levels too if
we think it makes sense (there is little overhead, and almost 0 if there
are no swaps).

One thing to note is that this pass does not recurse into control flow
blocks at all, it treats them as black box operations. So if any control
flow blocks contain swap gates those will not be optimized away. This
change was made because mapping the permutation outside and inside any
control flow block was larger in scope than what the intent for this
pass was. That type of work is better suited for the routing passes
which already have to reason about this.

* Update tests with optimization level 3

* Pass final layout from ElideSwap to output

The new ElideSwap pass is causing an output permutation just as a
routing pass would. This information needs to be passed through to the
output in case there is no layout or routing run. In those cases the
information about the output permutation caused by the swap elision will
be lost and doing layout aware operations like Operator.from_circuit()
will not be able to reason about the permutation. This commit fixes this
by inserting the original layout and qubit mapping into the property set
along with the final layout. Then the base pass class and pass manager
class are updated to use the original layout as the initial layout if
one isn't set. In cases where we run layout and routing the output
metadata from those passes will superscede these new metadata fields.

* Move pass in opt level 3 earlier in stage and skip with explicit layout

This commit fixes 2 issues in the execution of the new ElideSwaps pass
as part of optimization level 3. First we were running it too late in
the process both after high level synthesis (which isn't relavant yet,
but will be soon when this is expanded to elide all permutations not
just swaps) and also after reset diagonal before measurement. The second
issue is that if the user is specifying to run with a manual layout set
we should not run this pass, as it will interfere with the user intent.

* Doc and copy paste fixes

* Expand test coverage

* Update permutation tracking

There were 2 issues with the permutation tracking done in an earlier
commit. First, it conflicted with the final_layout property set via
routing (or internally by the routing done in the combined sabre
layout). This was breaking conditional checks in the preset pass manager
around embedding. To fix this a different property is used and set as
the output final layout if no final layout is set. The second issue was
the output layout object was not taking into account a set initial
layout which will permute the qubits and cause the output to not be up
to date. This is fixed by updating apply layout to apply the initial
layout to the elision_final_layout in the property set.

* Generalize pass to support PermutationGate too

This commit generalizes the pass from just considering swap gates to all
permutations (via the PermutationGate class). This enables the pass to
elide additional circuit permutations, not just the special case of a
swap gate. The pass and module are renamed accordingly to
ElidePermutations and elide_permutations respectively.

* Fix permutation handling

This commit fixes the recently added handling of the PermutationGate so
that it correctly is mapping the qubits. The previous iteration of this
messed up the mapping logic so it wasn't valid.

* Fix formatting

* Fix final layout handling for no initial layout

* Improve documentation and log a warning if run post layout

* Fix final layout handling with no ElideSwaps being run

* Fix docs build

* Fix release note

* Fix typo

* Add test for routing and elide permutations

* Apply suggestions from code review

Co-authored-by: Jim Garrison 

* Rename test file to test_elide_permutations.py

* Apply suggestions from code review

Co-authored-by: Kevin Hartman 

* Fix test import after rebase

* fixing failing test cases

this should pass CI after merging #12057

* addresses kehas comments - thx

* Adding FinalyzeLayouts pass to pull the virtual circuit permutation from ElidePermutations to the final layout

* formatting

* partial rebase on top of 12057 + tests

* also need test_operator for partial rebase

* removing from transpiler flow for now; reworking elide tests

* also adding permutation gate to the example

* also temporarily reverting test_transpiler.py

* update to release notes

* minor fixes

* Apply suggestions from code review

* Fix lint

* Update qiskit/transpiler/passes/optimization/elide_permutations.py

* Add test to test we skip after layout

* Integrate FinalizeLayouts into the PassManager harness

This commit integrates the function that finalize layouts was performing
into the passmanager harnesss. We'll always need to run the equivalent
of finalize layout if any passes are setting a virtual permutation so
using a standalone pass that can be forgotten is potentially error
prone. This inlines the logic as part of the passmanager's output
preparation stage so we always finalize the layout.

* Compose a potential existing virtual_permutation_layout

* Remove unused import

---------

Co-authored-by: Jim Garrison 
Co-authored-by: Kevin Hartman 
Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
Co-authored-by: AlexanderIvrii 
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
---
 qiskit/transpiler/passes/__init__.py          |   2 +
 .../transpiler/passes/layout/apply_layout.py  |   1 -
 qiskit/transpiler/passes/layout/set_layout.py |   2 +-
 .../passes/optimization/__init__.py           |   1 +
 .../passes/optimization/elide_permutations.py | 112 +++++
 qiskit/transpiler/passmanager.py              |  45 +-
 .../preset_passmanagers/builtin_plugins.py    |   1 -
 .../add-elide-swaps-b0a4c373c9af1efd.yaml     |  41 ++
 .../transpiler/test_elide_permutations.py     | 430 ++++++++++++++++++
 9 files changed, 631 insertions(+), 4 deletions(-)
 create mode 100644 qiskit/transpiler/passes/optimization/elide_permutations.py
 create mode 100644 releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml
 create mode 100644 test/python/transpiler/test_elide_permutations.py

diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py
index b2614624b415..c1e1705f1cce 100644
--- a/qiskit/transpiler/passes/__init__.py
+++ b/qiskit/transpiler/passes/__init__.py
@@ -87,6 +87,7 @@
    EchoRZXWeylDecomposition
    ResetAfterMeasureSimplification
    OptimizeCliffords
+   ElidePermutations
    NormalizeRXAngle
    OptimizeAnnotated
 
@@ -236,6 +237,7 @@
 from .optimization import CollectCliffords
 from .optimization import ResetAfterMeasureSimplification
 from .optimization import OptimizeCliffords
+from .optimization import ElidePermutations
 from .optimization import NormalizeRXAngle
 from .optimization import OptimizeAnnotated
 
diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py
index e87b6f7ce4e9..9cbedcef5ab3 100644
--- a/qiskit/transpiler/passes/layout/apply_layout.py
+++ b/qiskit/transpiler/passes/layout/apply_layout.py
@@ -56,7 +56,6 @@ def run(self, dag):
             raise TranspilerError("The 'layout' must be full (with ancilla).")
 
         post_layout = self.property_set["post_layout"]
-
         q = QuantumRegister(len(layout), "q")
 
         new_dag = DAGCircuit()
diff --git a/qiskit/transpiler/passes/layout/set_layout.py b/qiskit/transpiler/passes/layout/set_layout.py
index cfdc6d630df1..c4e5faa91fb5 100644
--- a/qiskit/transpiler/passes/layout/set_layout.py
+++ b/qiskit/transpiler/passes/layout/set_layout.py
@@ -63,7 +63,7 @@ def run(self, dag):
             layout = None
         else:
             raise InvalidLayoutError(
-                f"SetLayout was intialized with the layout type: {type(self.layout)}"
+                f"SetLayout was initialized with the layout type: {type(self.layout)}"
             )
         self.property_set["layout"] = layout
         return dag
diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py
index 40e877ec5147..082cb3f67ec9 100644
--- a/qiskit/transpiler/passes/optimization/__init__.py
+++ b/qiskit/transpiler/passes/optimization/__init__.py
@@ -35,5 +35,6 @@
 from .reset_after_measure_simplification import ResetAfterMeasureSimplification
 from .optimize_cliffords import OptimizeCliffords
 from .collect_cliffords import CollectCliffords
+from .elide_permutations import ElidePermutations
 from .normalize_rx_angle import NormalizeRXAngle
 from .optimize_annotated import OptimizeAnnotated
diff --git a/qiskit/transpiler/passes/optimization/elide_permutations.py b/qiskit/transpiler/passes/optimization/elide_permutations.py
new file mode 100644
index 000000000000..d9704ff0518b
--- /dev/null
+++ b/qiskit/transpiler/passes/optimization/elide_permutations.py
@@ -0,0 +1,112 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2023
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+
+"""Remove any swap gates in the circuit by pushing it through into a qubit permutation."""
+
+import logging
+
+from qiskit.circuit.library.standard_gates import SwapGate
+from qiskit.circuit.library.generalized_gates import PermutationGate
+from qiskit.transpiler.basepasses import TransformationPass
+from qiskit.transpiler.layout import Layout
+
+logger = logging.getLogger(__name__)
+
+
+class ElidePermutations(TransformationPass):
+    r"""Remove permutation operations from a pre-layout circuit
+
+    This pass is intended to be run before a layout (mapping virtual qubits
+    to physical qubits) is set during the transpilation pipeline. This
+    pass iterates over the :class:`~.DAGCircuit` and when a :class:`~.SwapGate`
+    or :class:`~.PermutationGate` are encountered it permutes the virtual qubits in
+    the circuit and removes the swap gate. This will effectively remove any
+    :class:`~SwapGate`\s or :class:`~PermutationGate` in the circuit prior to running
+    layout. If this pass is run after a layout has been set it will become a no-op
+    (and log a warning) as this optimization is not sound after physical qubits are
+    selected and there are connectivity constraints to adhere to.
+
+    For tracking purposes this pass sets 3 values in the property set if there
+    are any :class:`~.SwapGate` or :class:`~.PermutationGate` objects in the circuit
+    and the pass isn't a no-op.
+
+    * ``original_layout``: The trivial :class:`~.Layout` for the input to this pass being run
+    * ``original_qubit_indices``: The mapping of qubit objects to positional indices for the state
+        of the circuit as input to this pass.
+    * ``virtual_permutation_layout``: A :class:`~.Layout` object mapping input qubits to the output
+        state after eliding permutations.
+
+    These three properties are needed for the transpiler to track the permutations in the out
+    :attr:`.QuantumCircuit.layout` attribute. The elision of permutations is equivalent to a
+    ``final_layout`` set by routing and all three of these attributes are needed in the case
+    """
+
+    def run(self, dag):
+        """Run the ElidePermutations pass on ``dag``.
+
+        Args:
+            dag (DAGCircuit): the DAG to be optimized.
+
+        Returns:
+            DAGCircuit: the optimized DAG.
+        """
+        if self.property_set["layout"] is not None:
+            logger.warning(
+                "ElidePermutations is not valid after a layout has been set. This indicates "
+                "an invalid pass manager construction."
+            )
+            return dag
+
+        op_count = dag.count_ops(recurse=False)
+        if op_count.get("swap", 0) == 0 and op_count.get("permutation", 0) == 0:
+            return dag
+
+        new_dag = dag.copy_empty_like()
+        qubit_mapping = list(range(len(dag.qubits)))
+
+        def _apply_mapping(qargs):
+            return tuple(dag.qubits[qubit_mapping[dag.find_bit(qubit).index]] for qubit in qargs)
+
+        for node in dag.topological_op_nodes():
+            if not isinstance(node.op, (SwapGate, PermutationGate)):
+                new_dag.apply_operation_back(
+                    node.op, _apply_mapping(node.qargs), node.cargs, check=False
+                )
+            elif getattr(node.op, "condition", None) is not None:
+                new_dag.apply_operation_back(
+                    node.op, _apply_mapping(node.qargs), node.cargs, check=False
+                )
+            elif isinstance(node.op, SwapGate):
+                index_0 = dag.find_bit(node.qargs[0]).index
+                index_1 = dag.find_bit(node.qargs[1]).index
+                qubit_mapping[index_1], qubit_mapping[index_0] = (
+                    qubit_mapping[index_0],
+                    qubit_mapping[index_1],
+                )
+            elif isinstance(node.op, PermutationGate):
+                starting_indices = [qubit_mapping[dag.find_bit(qarg).index] for qarg in node.qargs]
+                pattern = node.op.params[0]
+                pattern_indices = [qubit_mapping[idx] for idx in pattern]
+                for i, j in zip(starting_indices, pattern_indices):
+                    qubit_mapping[i] = j
+        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_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
+        if current_layout := self.property_set["virtual_permutation_layout"] is not None:
+            self.property_set["virtual_permutation_layout"] = current_layout.compose(new_layout)
+        else:
+            self.property_set["virtual_permutation_layout"] = new_layout
+        return new_dag
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index 025c3ea9dfd6..bb1344e34cba 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -29,7 +29,7 @@
 from qiskit.passmanager.exceptions import PassManagerError
 from .basepasses import BasePass
 from .exceptions import TranspilerError
-from .layout import TranspileLayout
+from .layout import TranspileLayout, Layout
 
 _CircuitsT = Union[List[QuantumCircuit], QuantumCircuit]
 
@@ -69,6 +69,7 @@ def _passmanager_backend(
     ) -> QuantumCircuit:
         out_program = dag_to_circuit(passmanager_ir, copy_operations=False)
 
+        self._finalize_layouts(passmanager_ir)
         out_name = kwargs.get("output_name", None)
         if out_name is not None:
             out_program.name = out_name
@@ -96,6 +97,48 @@ def _passmanager_backend(
 
         return out_program
 
+    def _finalize_layouts(self, dag):
+        if (virtual_permutation_layout := self.property_set["virtual_permutation_layout"]) is None:
+            return
+
+        self.property_set.pop("virtual_permutation_layout")
+
+        # virtual_permutation_layout is usually created before extending the layout with ancillas,
+        # so we extend the permutation to be identity on ancilla qubits
+        original_qubit_indices = self.property_set.get("original_qubit_indices", None)
+        for oq in original_qubit_indices:
+            if oq not in virtual_permutation_layout:
+                virtual_permutation_layout[oq] = original_qubit_indices[oq]
+
+        t_qubits = dag.qubits
+
+        if (t_initial_layout := self.property_set.get("layout", None)) is None:
+            t_initial_layout = Layout(dict(enumerate(t_qubits)))
+
+        if (t_final_layout := self.property_set.get("final_layout", None)) is None:
+            t_final_layout = Layout(dict(enumerate(t_qubits)))
+
+        # Ordered list of original qubits
+        original_qubits_reverse = {v: k for k, v in original_qubit_indices.items()}
+        original_qubits = []
+        for i in range(len(original_qubits_reverse)):
+            original_qubits.append(original_qubits_reverse[i])
+
+        virtual_permutation_layout_inv = virtual_permutation_layout.inverse(
+            original_qubits, original_qubits
+        )
+
+        t_initial_layout_inv = t_initial_layout.inverse(original_qubits, t_qubits)
+
+        # ToDo: this can possibly be made simpler
+        new_final_layout = t_initial_layout_inv
+        new_final_layout = new_final_layout.compose(virtual_permutation_layout_inv, original_qubits)
+        new_final_layout = new_final_layout.compose(t_initial_layout, original_qubits)
+        new_final_layout = new_final_layout.compose(t_final_layout, t_qubits)
+
+        self.property_set["layout"] = t_initial_layout
+        self.property_set["final_layout"] = new_final_layout
+
     def append(
         self,
         passes: Task | list[Task],
diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
index fc01f6efaced..8eddb9adf63e 100644
--- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
+++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
@@ -154,7 +154,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
                 )
             )
             init.append(CommutativeCancellation())
-
         else:
             raise TranspilerError(f"Invalid optimization level {optimization_level}")
         return init
diff --git a/releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml b/releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml
new file mode 100644
index 000000000000..a8da2921990a
--- /dev/null
+++ b/releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml
@@ -0,0 +1,41 @@
+---
+features:
+  - |
+    Added a new optimization transpiler pass, :class:`~.ElidePermutations`,
+    which is designed to run prior to the :ref:`layout_stage` and will
+    optimize away any :class:`~.SwapGate`\s and
+    :class:`~qiskit.circuit.library.PermutationGate`\s
+    in a circuit by permuting virtual
+    qubits. For example, taking a circuit with :class:`~.SwapGate`\s:
+
+    .. plot::
+
+       from qiskit.circuit import QuantumCircuit
+
+       qc = QuantumCircuit(3)
+       qc.h(0)
+       qc.swap(0, 1)
+       qc.swap(2, 0)
+       qc.cx(1, 0)
+       qc.measure_all()
+       qc.draw("mpl")
+
+    will remove the swaps when the pass is run:
+
+    .. plot::
+       :include-source:
+
+       from qiskit.transpiler.passes import ElidePermutations
+       from qiskit.circuit import QuantumCircuit
+
+       qc = QuantumCircuit(3)
+       qc.h(0)
+       qc.swap(0, 1)
+       qc.swap(2, 0)
+       qc.cx(1, 0)
+       qc.measure_all()
+
+       ElidePermutations()(qc).draw("mpl")
+
+    The pass also sets the ``virtual_permutation_layout`` property set, storing
+    the permutation of the virtual qubits that was optimized away.
diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py
new file mode 100644
index 000000000000..d3d807834fc0
--- /dev/null
+++ b/test/python/transpiler/test_elide_permutations.py
@@ -0,0 +1,430 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2023
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Test ElidePermutations pass"""
+
+import unittest
+
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.circuit.library.generalized_gates import PermutationGate
+from qiskit.transpiler.passes.optimization.elide_permutations import ElidePermutations
+from qiskit.circuit.controlflow import IfElseOp
+from qiskit.quantum_info import Operator
+from qiskit.transpiler.coupling import CouplingMap
+from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
+from test import QiskitTestCase  # pylint: disable=wrong-import-order
+
+
+class TestElidePermutations(QiskitTestCase):
+    """Test elide permutations logical optimization pass."""
+
+    def setUp(self):
+        super().setUp()
+        self.swap_pass = ElidePermutations()
+
+    def test_no_swap(self):
+        """Test no swap means no transform."""
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure_all()
+        res = self.swap_pass(qc)
+        self.assertEqual(res, qc)
+
+    def test_swap_in_middle(self):
+        """Test swap in middle of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.swap(0, 1)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(0, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(1, 0)
+        expected.measure(0, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_at_beginning(self):
+        """Test swap in beginning of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.swap(0, 1)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(1)
+        expected.cx(0, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(1, 0)
+        expected.measure(0, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_at_end(self):
+        """Test swap at the end of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+        qc.swap(0, 1)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(0, 0)
+        expected.measure(1, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_multiple_swaps(self):
+        """Test quantum circuit with multiple swaps."""
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.swap(0, 2)
+        qc.cx(0, 1)
+        qc.swap(1, 0)
+        qc.h(1)
+
+        expected = QuantumCircuit(3)
+        expected.h(0)
+        expected.cx(2, 1)
+        expected.h(2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_before_measure(self):
+        """Test swap before measure is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.swap(0, 1)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(1, 0)
+        expected.measure(0, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_if_else_block(self):
+        """Test swap elision only happens outside control flow."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        with qc.if_test((0, 0)):
+            qc.swap(0, 1)
+        qc.cx(0, 1)
+        res = self.swap_pass(qc)
+        self.assertEqual(res, qc)
+
+    def test_swap_if_else_block_with_outside_swap(self):
+        """Test swap elision only happens outside control flow."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.swap(2, 0)
+        body = QuantumCircuit(2)
+        body.swap(0, 1)
+        if_else_op = IfElseOp((qc.clbits[0], 0), body)
+
+        qc.append(if_else_op, [0, 1])
+        qc.cx(0, 1)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.append(IfElseOp((expected.clbits[0], 0), body), [2, 1])
+        expected.cx(2, 1)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_condition(self):
+        """Test swap elision doesn't touch conditioned swap."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.swap(0, 1).c_if(qc.clbits[0], 0)
+        qc.cx(0, 1)
+        res = self.swap_pass(qc)
+        self.assertEqual(res, qc)
+
+    def test_permutation_in_middle(self):
+        """Test permutation in middle of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.append(PermutationGate([2, 1, 0]), [0, 1, 2])
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 0)
+        expected.barrier(0, 1, 2)
+        expected.measure(2, 0)
+        expected.measure(1, 1)
+        expected.measure(0, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_permutation_at_beginning(self):
+        """Test permutation in beginning of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.append(PermutationGate([2, 1, 0]), [0, 1, 2])
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(2)
+        expected.cx(1, 0)
+        expected.barrier(0, 1, 2)
+        expected.measure(2, 0)
+        expected.measure(1, 1)
+        expected.measure(0, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_permutation_at_end(self):
+        """Test permutation at end of bell is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+        qc.append(PermutationGate([2, 1, 0]), [0, 1, 2])
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(0, 0)
+        expected.measure(1, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_swap_and_permutation(self):
+        """Test a combination of swap and permutation gates."""
+        qc = QuantumCircuit(3, 3)
+        qc.append(PermutationGate([2, 1, 0]), [0, 1, 2])
+        qc.swap(0, 2)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(0, 0)
+        expected.measure(1, 1)
+        expected.measure(2, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+    def test_permutation_before_measure(self):
+        """Test permutation before measure is elided."""
+        qc = QuantumCircuit(3, 3)
+        qc.h(0)
+        qc.cx(1, 2)
+        qc.barrier(0, 1, 2)
+        qc.append(PermutationGate([1, 2, 0]), [0, 1, 2])
+        qc.measure(0, 0)
+        qc.measure(1, 1)
+        qc.measure(2, 2)
+
+        expected = QuantumCircuit(3, 3)
+        expected.h(0)
+        expected.cx(1, 2)
+        expected.barrier(0, 1, 2)
+        expected.measure(1, 0)
+        expected.measure(2, 1)
+        expected.measure(0, 2)
+
+        res = self.swap_pass(qc)
+        self.assertEqual(res, expected)
+
+
+class TestElidePermutationsInTranspileFlow(QiskitTestCase):
+    """
+    Test elide permutations in the full transpile pipeline, especially that
+    "layout" and "final_layout" attributes are updated correctly
+    as to preserve unitary equivalence.
+    """
+
+    def test_not_run_after_layout(self):
+        """Test ElidePermutations doesn't do anything after layout."""
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.swap(0, 2)
+        qc.cx(0, 1)
+        qc.swap(1, 0)
+        qc.h(1)
+
+        spm = generate_preset_pass_manager(
+            optimization_level=3, initial_layout=list(range(2, -1, -1)), seed_transpiler=42
+        )
+        spm.layout += ElidePermutations()
+        res = spm.run(qc)
+        self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+        self.assertIn("swap", res.count_ops())
+        self.assertTrue(res.layout.final_index_layout(), [0, 1, 2])
+
+    def test_unitary_equivalence(self):
+        """Test unitary equivalence of the original and transpiled circuits."""
+        qc = QuantumCircuit(3)
+        qc.h(0)
+        qc.swap(0, 2)
+        qc.cx(0, 1)
+        qc.swap(1, 0)
+        qc.h(1)
+
+        with self.subTest("no coupling map"):
+            spm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+        with self.subTest("with coupling map"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3, seed_transpiler=42, coupling_map=CouplingMap.from_line(3)
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+    def test_unitary_equivalence_routing_and_basis_translation(self):
+        """Test on a larger example that includes routing and basis translation."""
+
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.swap(0, 2)
+        qc.cx(0, 1)
+        qc.swap(1, 0)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.append(PermutationGate([0, 2, 1]), [0, 1, 2])
+        qc.h(1)
+
+        with self.subTest("no coupling map"):
+            spm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+        with self.subTest("with coupling map"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=1234,
+                coupling_map=CouplingMap.from_line(5),
+                basis_gates=["u", "cz"],
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+        with self.subTest("no coupling map but with initial layout"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=1234,
+                initial_layout=[4, 2, 1, 3, 0],
+                basis_gates=["u", "cz"],
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+        with self.subTest("coupling map and initial layout"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=1234,
+                initial_layout=[4, 2, 1, 3, 0],
+                basis_gates=["u", "cz"],
+                coupling_map=CouplingMap.from_line(5),
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
+        with self.subTest("larger coupling map"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=42,
+                coupling_map=CouplingMap.from_line(8),
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+
+            qc_with_ancillas = QuantumCircuit(8)
+            qc_with_ancillas.append(qc, [0, 1, 2, 3, 4])
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc_with_ancillas)))
+
+        with self.subTest("larger coupling map and initial layout"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=42,
+                initial_layout=[4, 2, 7, 3, 6],
+                coupling_map=CouplingMap.from_line(8),
+            )
+            spm.init += ElidePermutations()
+            res = spm.run(qc)
+
+            qc_with_ancillas = QuantumCircuit(8)
+            qc_with_ancillas.append(qc, [0, 1, 2, 3, 4])
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc_with_ancillas)))
+
+
+if __name__ == "__main__":
+    unittest.main()

From 8ba79743ee022c118e3d0dd78d91ecb7cc629b71 Mon Sep 17 00:00:00 2001
From: Ian Hincks 
Date: Thu, 2 May 2024 10:19:32 -0400
Subject: [PATCH 059/179] Update make_data_bin() to return DataBin proper
 (#12219)

* Update make_data_bin() to return DataBin proper

* Remove metaclass

I checked all of our repos and it's not used by anything

* fix implementations

* remove make_data_bin usage

* make the DataBin follow the Shaped protocol

* add it back to containers module (removed by accident)

* get implemntatinos to assign shape

* improve the repr

* add reno

* fix reno formatting

* improve error string

* use views instead of sequences

* fix tests

---------

Co-authored-by: Takashi Imamichi 
---
 qiskit/primitives/backend_estimator_v2.py     |   5 +-
 qiskit/primitives/backend_sampler_v2.py       |   9 +-
 qiskit/primitives/base/base_estimator.py      |  13 +-
 qiskit/primitives/containers/data_bin.py      | 185 +++++++++++-------
 qiskit/primitives/statevector_estimator.py    |   8 +-
 qiskit/primitives/statevector_sampler.py      |   9 +-
 ...databin-construction-72ec041075410cb2.yaml |  16 ++
 .../primitives/containers/test_data_bin.py    |  74 ++++---
 .../containers/test_primitive_result.py       |  11 +-
 .../primitives/containers/test_pub_result.py  |  12 +-
 .../primitives/test_backend_sampler_v2.py     |   5 +-
 .../primitives/test_statevector_sampler.py    |   5 +-
 12 files changed, 190 insertions(+), 162 deletions(-)
 create mode 100644 releasenotes/notes/databin-construction-72ec041075410cb2.yaml

diff --git a/qiskit/primitives/backend_estimator_v2.py b/qiskit/primitives/backend_estimator_v2.py
index 9afc6d892f3d..d94d3674e9ad 100644
--- a/qiskit/primitives/backend_estimator_v2.py
+++ b/qiskit/primitives/backend_estimator_v2.py
@@ -31,7 +31,7 @@
 
 from .backend_estimator import _pauli_expval_with_variance, _prepare_counts, _run_circuits
 from .base import BaseEstimatorV2
-from .containers import EstimatorPubLike, PrimitiveResult, PubResult
+from .containers import DataBin, EstimatorPubLike, PrimitiveResult, PubResult
 from .containers.bindings_array import BindingsArray
 from .containers.estimator_pub import EstimatorPub
 from .primitive_job import PrimitiveJob
@@ -256,8 +256,7 @@ def _postprocess_pub(
                 evs[index] += expval * coeff
                 variances[index] += variance * coeff**2
         stds = np.sqrt(variances / shots)
-        data_bin_cls = self._make_data_bin(pub)
-        data_bin = data_bin_cls(evs=evs, stds=stds)
+        data_bin = DataBin(evs=evs, stds=stds, shape=evs.shape)
         return PubResult(data_bin, metadata={"target_precision": pub.precision})
 
     def _bind_and_add_measurements(
diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py
index 87507e1d54d0..3b560645af6d 100644
--- a/qiskit/primitives/backend_sampler_v2.py
+++ b/qiskit/primitives/backend_sampler_v2.py
@@ -27,10 +27,10 @@
 from qiskit.primitives.base import BaseSamplerV2
 from qiskit.primitives.containers import (
     BitArray,
+    DataBin,
     PrimitiveResult,
     PubResult,
     SamplerPubLike,
-    make_data_bin,
 )
 from qiskit.primitives.containers.bit_array import _min_num_bytes
 from qiskit.primitives.containers.sampler_pub import SamplerPub
@@ -210,15 +210,10 @@ def _postprocess_pub(
                 ary = _samples_to_packed_array(samples, item.num_bits, item.start)
                 arrays[item.creg_name][index] = ary
 
-        data_bin_cls = make_data_bin(
-            [(item.creg_name, BitArray) for item in meas_info],
-            shape=shape,
-        )
         meas = {
             item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
         }
-        data_bin = data_bin_cls(**meas)
-        return PubResult(data_bin, metadata={})
+        return PubResult(DataBin(**meas, shape=shape), metadata={})
 
 
 def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]:
diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py
index 47c0ba10bf0c..9191b4162d92 100644
--- a/qiskit/primitives/base/base_estimator.py
+++ b/qiskit/primitives/base/base_estimator.py
@@ -18,8 +18,6 @@
 from collections.abc import Iterable, Sequence
 from copy import copy
 from typing import Generic, TypeVar
-import numpy as np
-from numpy.typing import NDArray
 
 from qiskit.circuit import QuantumCircuit
 from qiskit.providers import JobV1 as Job
@@ -27,7 +25,6 @@
 from qiskit.quantum_info.operators.base_operator import BaseOperator
 
 from ..containers import (
-    make_data_bin,
     DataBin,
     EstimatorPubLike,
     PrimitiveResult,
@@ -205,12 +202,10 @@ class BaseEstimatorV2(ABC):
     """
 
     @staticmethod
-    def _make_data_bin(pub: EstimatorPub) -> DataBin:
-        # provide a standard way to construct estimator databins to ensure that names match
-        # across implementations
-        return make_data_bin(
-            (("evs", NDArray[np.float64]), ("stds", NDArray[np.float64])), pub.shape
-        )
+    def _make_data_bin(_: EstimatorPub) -> type[DataBin]:
+        # this method is present for backwards compat. new primitive implementatinos
+        # should avoid it.
+        return DataBin
 
     @abstractmethod
     def run(
diff --git a/qiskit/primitives/containers/data_bin.py b/qiskit/primitives/containers/data_bin.py
index 50934b6cdfd3..5ea31f7510e6 100644
--- a/qiskit/primitives/containers/data_bin.py
+++ b/qiskit/primitives/containers/data_bin.py
@@ -15,110 +15,151 @@
 """
 from __future__ import annotations
 
-from collections.abc import Iterable, Sequence
-from dataclasses import make_dataclass
-from typing import Any
+from typing import Any, ItemsView, Iterable, KeysView, ValuesView
 
+import numpy as np
 
-class DataBinMeta(type):
-    """Metaclass for :class:`DataBin` that adds the shape to the type name.
+from .shape import ShapedMixin, ShapeInput, shape_tuple
 
-    This is so that the class has a custom repr with DataBin<*shape> notation.
-    """
 
-    def __repr__(cls):
-        name = cls.__name__
-        if cls._SHAPE is None:
-            return name
-        shape = ",".join(map(str, cls._SHAPE))
-        return f"{name}<{shape}>"
+def _value_repr(value: Any) -> str:
+    """Helper function for :meth:`DataBin.__repr__`."""
+    if isinstance(value, np.ndarray):
+        return f"np.ndarray()"
+    return repr(value)
+
+
+class DataBin(ShapedMixin):
+    """Namespace for storing data.
+
+    .. code-block:: python
 
+        data = DataBin(
+            alpha=BitArray.from_bitstrings(["0010"]),
+            beta=np.array([1.2])
+        )
 
-class DataBin(metaclass=DataBinMeta):
-    """Base class for data bin containers.
+        print("alpha data:", data.alpha)
+        print("beta data:", data.beta)
 
-    Subclasses are typically made via :class:`~make_data_bin`, which is a specialization of
-    :class:`make_dataclass`.
     """
 
-    _RESTRICTED_NAMES = {
-        "_RESTRICTED_NAMES",
-        "_SHAPE",
-        "_FIELDS",
-        "_FIELD_TYPES",
-        "keys",
-        "values",
-        "items",
-    }
-    _SHAPE: tuple[int, ...] | None = None
-    _FIELDS: tuple[str, ...] = ()
-    """The fields allowed in this data bin."""
-    _FIELD_TYPES: tuple[type, ...] = ()
-    """The types of each field."""
+    __slots__ = ("_data", "_shape")
+
+    _RESTRICTED_NAMES = frozenset(
+        {
+            "_RESTRICTED_NAMES",
+            "_SHAPE",
+            "_FIELDS",
+            "_FIELD_TYPES",
+            "_data",
+            "_shape",
+            "keys",
+            "values",
+            "items",
+            "shape",
+            "ndim",
+            "size",
+        }
+    )
+
+    def __init__(self, *, shape: ShapeInput = (), **data):
+        """
+        Args:
+            data: Name/value data to place in the data bin.
+            shape: The leading shape common to all entries in the data bin. This defaults to
+                the trivial leading shape of ``()`` that is compatible with all objects.
+
+        Raises:
+            ValueError: If a name overlaps with a method name on this class.
+            ValueError: If some value is inconsistent with the provided shape.
+        """
+        if not self._RESTRICTED_NAMES.isdisjoint(data):
+            bad_names = sorted(self._RESTRICTED_NAMES.intersection(data))
+            raise ValueError(f"Cannot assign with these field names: {bad_names}")
+
+        _setattr = super().__setattr__
+        _setattr("_shape", shape_tuple(shape))
+        _setattr("_data", data)
+
+        ndim = len(self._shape)
+        for name, value in data.items():
+            if getattr(value, "shape", shape)[:ndim] != shape:
+                raise ValueError(f"The value of '{name}' does not lead with the shape {shape}.")
+            _setattr(name, value)
+
+        super().__init__()
 
     def __len__(self):
-        return len(self._FIELDS)
+        return len(self._data)
+
+    def __setattr__(self, *_):
+        raise NotImplementedError
 
     def __repr__(self):
-        vals = (f"{name}={getattr(self, name)}" for name in self._FIELDS if hasattr(self, name))
-        return f"{type(self)}({', '.join(vals)})"
+        vals = [f"{name}={_value_repr(val)}" for name, val in self.items()]
+        if self.ndim:
+            vals.append(f"shape={self.shape}")
+        return f"{type(self).__name__}({', '.join(vals)})"
 
     def __getitem__(self, key: str) -> Any:
-        if key not in self._FIELDS:
-            raise KeyError(f"Key ({key}) does not exist in this data bin.")
-        return getattr(self, key)
+        try:
+            return self._data[key]
+        except KeyError as ex:
+            raise KeyError(f"Key ({key}) does not exist in this data bin.") from ex
 
     def __contains__(self, key: str) -> bool:
-        return key in self._FIELDS
+        return key in self._data
 
     def __iter__(self) -> Iterable[str]:
-        return iter(self._FIELDS)
+        return iter(self._data)
 
-    def keys(self) -> Sequence[str]:
-        """Return a list of field names."""
-        return tuple(self._FIELDS)
+    def keys(self) -> KeysView[str]:
+        """Return a view of field names."""
+        return self._data.keys()
 
-    def values(self) -> Sequence[Any]:
-        """Return a list of values."""
-        return tuple(getattr(self, key) for key in self._FIELDS)
+    def values(self) -> ValuesView[Any]:
+        """Return a view of values."""
+        return self._data.values()
 
-    def items(self) -> Sequence[tuple[str, Any]]:
-        """Return a list of field names and values"""
-        return tuple((key, getattr(self, key)) for key in self._FIELDS)
+    def items(self) -> ItemsView[str, Any]:
+        """Return a view of field names and values"""
+        return self._data.items()
 
+    # The following properties exist to provide support to legacy private class attributes which
+    # gained widespread prior to qiskit 1.1. These properties will be removed once the internal
+    # projects have made the appropriate changes.
 
+    @property
+    def _FIELDS(self) -> tuple[str, ...]:  # pylint: disable=invalid-name
+        return tuple(self._data)
+
+    @property
+    def _FIELD_TYPES(self) -> tuple[Any, ...]:  # pylint: disable=invalid-name
+        return tuple(map(type, self.values()))
+
+    @property
+    def _SHAPE(self) -> tuple[int, ...]:  # pylint: disable=invalid-name
+        return self.shape
+
+
+# pylint: disable=unused-argument
 def make_data_bin(
     fields: Iterable[tuple[str, type]], shape: tuple[int, ...] | None = None
 ) -> type[DataBin]:
-    """Return a new subclass of :class:`~DataBin` with the provided fields and shape.
+    """Return the :class:`~DataBin` type.
 
-    .. code-block:: python
-
-        my_bin = make_data_bin([("alpha", np.NDArray[np.float64])], shape=(20, 30))
-
-        # behaves like a dataclass
-        my_bin(alpha=np.empty((20, 30)))
+    .. note::
+        This class used to return a subclass of :class:`~DataBin`. However, that caused confusion
+        and didn't have a useful purpose. Several internal projects made use of this internal
+        function prior to qiskit 1.1. This function will be removed once these internal projects
+        have made the appropriate changes.
 
     Args:
         fields: Tuples ``(name, type)`` specifying the attributes of the returned class.
         shape: The intended shape of every attribute of this class.
 
     Returns:
-        A new class.
+        The :class:`DataBin` type.
     """
-    field_names, field_types = zip(*fields) if fields else ((), ())
-    for name in field_names:
-        if name in DataBin._RESTRICTED_NAMES:
-            raise ValueError(f"'{name}' is a restricted name for a DataBin.")
-    cls = make_dataclass(
-        "DataBin",
-        dict(zip(field_names, field_types)),
-        bases=(DataBin,),
-        frozen=True,
-        unsafe_hash=True,
-        repr=False,
-    )
-    cls._SHAPE = shape
-    cls._FIELDS = field_names
-    cls._FIELD_TYPES = field_types
-    return cls
+    return DataBin
diff --git a/qiskit/primitives/statevector_estimator.py b/qiskit/primitives/statevector_estimator.py
index a3b2cddefdd7..a5dc029edf73 100644
--- a/qiskit/primitives/statevector_estimator.py
+++ b/qiskit/primitives/statevector_estimator.py
@@ -22,7 +22,7 @@
 from qiskit.quantum_info import SparsePauliOp, Statevector
 
 from .base import BaseEstimatorV2
-from .containers import EstimatorPubLike, PrimitiveResult, PubResult
+from .containers import DataBin, EstimatorPubLike, PrimitiveResult, PubResult
 from .containers.estimator_pub import EstimatorPub
 from .primitive_job import PrimitiveJob
 from .utils import bound_circuit_to_instruction
@@ -160,6 +160,6 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult:
                     raise ValueError("Given operator is not Hermitian and noise cannot be added.")
                 expectation_value = rng.normal(expectation_value, precision)
             evs[index] = expectation_value
-        data_bin_cls = self._make_data_bin(pub)
-        data_bin = data_bin_cls(evs=evs, stds=stds)
-        return PubResult(data_bin, metadata={"precision": precision})
+
+        data = DataBin(evs=evs, stds=stds, shape=evs.shape)
+        return PubResult(data, metadata={"precision": precision})
diff --git a/qiskit/primitives/statevector_sampler.py b/qiskit/primitives/statevector_sampler.py
index d04eb3894ffc..c78865aec7e6 100644
--- a/qiskit/primitives/statevector_sampler.py
+++ b/qiskit/primitives/statevector_sampler.py
@@ -30,10 +30,10 @@
 from .base.validation import _has_measure
 from .containers import (
     BitArray,
+    DataBin,
     PrimitiveResult,
     PubResult,
     SamplerPubLike,
-    make_data_bin,
 )
 from .containers.sampler_pub import SamplerPub
 from .containers.bit_array import _min_num_bytes
@@ -194,15 +194,10 @@ def _run_pub(self, pub: SamplerPub) -> PubResult:
                 ary = _samples_to_packed_array(samples_array, item.num_bits, item.qreg_indices)
                 arrays[item.creg_name][index] = ary
 
-        data_bin_cls = make_data_bin(
-            [(item.creg_name, BitArray) for item in meas_info],
-            shape=bound_circuits.shape,
-        )
         meas = {
             item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
         }
-        data_bin = data_bin_cls(**meas)
-        return PubResult(data_bin, metadata={"shots": pub.shots})
+        return PubResult(DataBin(**meas, shape=pub.shape), metadata={"shots": pub.shots})
 
 
 def _preprocess_circuit(circuit: QuantumCircuit):
diff --git a/releasenotes/notes/databin-construction-72ec041075410cb2.yaml b/releasenotes/notes/databin-construction-72ec041075410cb2.yaml
new file mode 100644
index 000000000000..a9dbd60298fc
--- /dev/null
+++ b/releasenotes/notes/databin-construction-72ec041075410cb2.yaml
@@ -0,0 +1,16 @@
+---
+features_primitives:
+  - |
+    `qiskit.primitives.containers.DataBin` now satisfies the `qiskit.primitives.containers.Shaped` 
+    protocol. This means that every `DataBin` instance now has the additional attributes
+    * `shape: tuple[int, ...]` the leading shape of every entry in the instance
+    * `ndim: int` the length of `shape`
+    * `size: int` the product of the entries of `shape`
+    The shape can be passed to the constructor.
+upgrade_primitives:
+  - |
+    The function `qiskit.primitives.containers.make_data_bin()` no longer creates and returns a 
+    `qiskit.primitives.containers.DataBin` subclass. It instead always returns the `DataBin` class.
+    However, it continues to exist for backwards compatibility, though will eventually be deprecated. 
+    All users should migrate to construct `DataBin` instances directly, instead of instantiating 
+    subclasses as output by `make_data_bin()`.
diff --git a/test/python/primitives/containers/test_data_bin.py b/test/python/primitives/containers/test_data_bin.py
index b750174b7b6f..a8f802ebaeb1 100644
--- a/test/python/primitives/containers/test_data_bin.py
+++ b/test/python/primitives/containers/test_data_bin.py
@@ -15,65 +15,61 @@
 
 
 import numpy as np
-import numpy.typing as npt
 
-from qiskit.primitives.containers import make_data_bin
-from qiskit.primitives.containers.data_bin import DataBin, DataBinMeta
+from qiskit.primitives.containers.data_bin import DataBin
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
 class DataBinTestCase(QiskitTestCase):
     """Test the DataBin class."""
 
-    def test_make_databin(self):
-        """Test the make_databin() function."""
-        data_bin_cls = make_data_bin(
-            [("alpha", npt.NDArray[np.uint16]), ("beta", np.ndarray)], shape=(10, 20)
-        )
-
-        self.assertTrue(issubclass(type(data_bin_cls), DataBinMeta))
-        self.assertTrue(issubclass(data_bin_cls, DataBin))
-        self.assertEqual(data_bin_cls._FIELDS, ("alpha", "beta"))
-        self.assertEqual(data_bin_cls._FIELD_TYPES, (npt.NDArray[np.uint16], np.ndarray))
+    def test_make_databin_no_fields(self):
+        """Test DataBin when no fields are given."""
+        data_bin = DataBin()
+        self.assertEqual(len(data_bin), 0)
+        self.assertEqual(data_bin.shape, ())
 
+    def test_data_bin_basic(self):
+        """Test DataBin function basic access."""
         alpha = np.empty((10, 20), dtype=np.uint16)
         beta = np.empty((10, 20), dtype=int)
-        my_bin = data_bin_cls(alpha, beta)
+        my_bin = DataBin(alpha=alpha, beta=beta)
+
         self.assertEqual(len(my_bin), 2)
         self.assertTrue(np.all(my_bin.alpha == alpha))
         self.assertTrue(np.all(my_bin.beta == beta))
         self.assertTrue("alpha=" in str(my_bin))
-        self.assertTrue(str(my_bin).startswith("DataBin<10,20>"))
+        self.assertTrue(str(my_bin).startswith("DataBin"))
+        self.assertEqual(my_bin._FIELDS, ("alpha", "beta"))
+        self.assertEqual(my_bin._FIELD_TYPES, (np.ndarray, np.ndarray))
 
-        my_bin = data_bin_cls(beta=beta, alpha=alpha)
+        my_bin = DataBin(beta=beta, alpha=alpha)
         self.assertTrue(np.all(my_bin.alpha == alpha))
         self.assertTrue(np.all(my_bin.beta == beta))
 
-    def test_make_databin_no_shape(self):
-        """Test the make_databin() function with no shape."""
-        data_bin_cls = make_data_bin([("alpha", dict), ("beta", int)])
+    def test_constructor_failures(self):
+        """Test that the constructor fails when expected."""
 
-        self.assertTrue(issubclass(type(data_bin_cls), DataBinMeta))
-        self.assertTrue(issubclass(data_bin_cls, DataBin))
-        self.assertEqual(data_bin_cls._FIELDS, ("alpha", "beta"))
-        self.assertEqual(data_bin_cls._FIELD_TYPES, (dict, int))
+        with self.assertRaisesRegex(ValueError, "Cannot assign with these field names"):
+            DataBin(values=6)
 
-        my_bin = data_bin_cls({1: 2}, 5)
-        self.assertEqual(my_bin.alpha, {1: 2})
-        self.assertEqual(my_bin.beta, 5)
-        self.assertTrue("alpha=" in str(my_bin))
-        self.assertTrue(">" not in str(my_bin))
+        with self.assertRaisesRegex(ValueError, "does not lead with the shape"):
+            DataBin(x=np.empty((5,)), shape=(1,))
 
-    def test_make_databin_no_fields(self):
-        """Test the make_data_bin() function when no fields are given."""
-        data_bin_cls = make_data_bin([])
-        data_bin = data_bin_cls()
-        self.assertEqual(len(data_bin), 0)
+        with self.assertRaisesRegex(ValueError, "does not lead with the shape"):
+            DataBin(x=np.empty((5, 2, 3)), shape=(5, 2, 3, 4))
+
+    def test_shape(self):
+        """Test shape setting and attributes."""
+        databin = DataBin(x=6, y=np.empty((2, 3)))
+        self.assertEqual(databin.shape, ())
+
+        databin = DataBin(x=np.empty((5, 2)), y=np.empty((5, 2, 6)), shape=(5, 2))
+        self.assertEqual(databin.shape, (5, 2))
 
     def test_make_databin_mapping(self):
-        """Test the make_data_bin() function with mapping features."""
-        data_bin_cls = make_data_bin([("alpha", int), ("beta", dict)])
-        data_bin = data_bin_cls(10, {1: 2})
+        """Test DataBin with mapping features."""
+        data_bin = DataBin(alpha=10, beta={1: 2})
         self.assertEqual(len(data_bin), 2)
 
         with self.subTest("iterator"):
@@ -86,14 +82,14 @@ def test_make_databin_mapping(self):
                 _ = next(iterator)
 
         with self.subTest("keys"):
-            lst = data_bin.keys()
+            lst = list(data_bin.keys())
             key = lst[0]
             self.assertEqual(key, "alpha")
             key = lst[1]
             self.assertEqual(key, "beta")
 
         with self.subTest("values"):
-            lst = data_bin.values()
+            lst = list(data_bin.values())
             val = lst[0]
             self.assertIsInstance(val, int)
             self.assertEqual(val, 10)
@@ -102,7 +98,7 @@ def test_make_databin_mapping(self):
             self.assertEqual(val, {1: 2})
 
         with self.subTest("items"):
-            lst = data_bin.items()
+            lst = list(data_bin.items())
             key, val = lst[0]
             self.assertEqual(key, "alpha")
             self.assertIsInstance(val, int)
diff --git a/test/python/primitives/containers/test_primitive_result.py b/test/python/primitives/containers/test_primitive_result.py
index 93e563379c26..fc8c774a1649 100644
--- a/test/python/primitives/containers/test_primitive_result.py
+++ b/test/python/primitives/containers/test_primitive_result.py
@@ -14,9 +14,8 @@
 """Unit tests for PrimitiveResult."""
 
 import numpy as np
-import numpy.typing as npt
 
-from qiskit.primitives.containers import PrimitiveResult, PubResult, make_data_bin
+from qiskit.primitives.containers import DataBin, PrimitiveResult, PubResult
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -25,16 +24,12 @@ class PrimitiveResultCase(QiskitTestCase):
 
     def test_primitive_result(self):
         """Test the PrimitiveResult class."""
-        data_bin_cls = make_data_bin(
-            [("alpha", npt.NDArray[np.uint16]), ("beta", np.ndarray)], shape=(10, 20)
-        )
-
         alpha = np.empty((10, 20), dtype=np.uint16)
         beta = np.empty((10, 20), dtype=int)
 
         pub_results = [
-            PubResult(data_bin_cls(alpha, beta)),
-            PubResult(data_bin_cls(alpha, beta)),
+            PubResult(DataBin(alpha=alpha, beta=beta, shape=(10, 20))),
+            PubResult(DataBin(alpha=alpha, beta=beta, shape=(10, 20))),
         ]
         result = PrimitiveResult(pub_results, {"x": 2})
 
diff --git a/test/python/primitives/containers/test_pub_result.py b/test/python/primitives/containers/test_pub_result.py
index 1849b77c4757..011110df3bd9 100644
--- a/test/python/primitives/containers/test_pub_result.py
+++ b/test/python/primitives/containers/test_pub_result.py
@@ -13,7 +13,7 @@
 
 """Unit tests for PubResult."""
 
-from qiskit.primitives.containers import PubResult, make_data_bin
+from qiskit.primitives.containers import DataBin, PubResult
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -22,13 +22,12 @@ class PubResultCase(QiskitTestCase):
 
     def test_construction(self):
         """Test that the constructor works."""
-        data_bin = make_data_bin((("a", float), ("b", int)))
-        pub_result = PubResult(data_bin(a=1.0, b=2))
+        pub_result = PubResult(DataBin(a=1.0, b=2))
         self.assertEqual(pub_result.data.a, 1.0)
         self.assertEqual(pub_result.data.b, 2)
         self.assertEqual(pub_result.metadata, {})
 
-        pub_result = PubResult(data_bin(a=1.0, b=2), {"x": 1})
+        pub_result = PubResult(DataBin(a=1.0, b=2), {"x": 1})
         self.assertEqual(pub_result.data.a, 1.0)
         self.assertEqual(pub_result.data.b, 2)
         self.assertEqual(pub_result.metadata, {"x": 1})
@@ -38,6 +37,5 @@ def test_repr(self):
         # we are primarily interested in making sure some future change doesn't cause the repr to
         # raise an error. it is more sensible for humans to detect a deficiency in the formatting
         # itself, should one be uncovered
-        data_bin = make_data_bin((("a", float), ("b", int)))
-        self.assertTrue(repr(PubResult(data_bin(a=1.0, b=2))).startswith("PubResult"))
-        self.assertTrue(repr(PubResult(data_bin(a=1.0, b=2), {"x": 1})).startswith("PubResult"))
+        self.assertTrue(repr(PubResult(DataBin(a=1.0, b=2))).startswith("PubResult"))
+        self.assertTrue(repr(PubResult(DataBin(a=1.0, b=2), {"x": 1})).startswith("PubResult"))
diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py
index dd58920689a3..9f6c007b1d5b 100644
--- a/test/python/primitives/test_backend_sampler_v2.py
+++ b/test/python/primitives/test_backend_sampler_v2.py
@@ -15,7 +15,6 @@
 from __future__ import annotations
 
 import unittest
-from dataclasses import astuple
 from test import QiskitTestCase, combine
 
 import numpy as np
@@ -604,7 +603,7 @@ def test_circuit_with_multiple_cregs(self, backend):
                 result = sampler.run([qc], shots=self._shots).result()
                 self.assertEqual(len(result), 1)
                 data = result[0].data
-                self.assertEqual(len(astuple(data)), 3)
+                self.assertEqual(len(data), 3)
                 for creg in qc.cregs:
                     self.assertTrue(hasattr(data, creg.name))
                     self._assert_allclose(getattr(data, creg.name), np.array(target[creg.name]))
@@ -640,7 +639,7 @@ def test_circuit_with_aliased_cregs(self, backend):
         result = sampler.run([qc2], shots=self._shots).result()
         self.assertEqual(len(result), 1)
         data = result[0].data
-        self.assertEqual(len(astuple(data)), 3)
+        self.assertEqual(len(data), 3)
         for creg_name in target:
             self.assertTrue(hasattr(data, creg_name))
             self._assert_allclose(getattr(data, creg_name), np.array(target[creg_name]))
diff --git a/test/python/primitives/test_statevector_sampler.py b/test/python/primitives/test_statevector_sampler.py
index 1a8ed0402e58..de17b2824075 100644
--- a/test/python/primitives/test_statevector_sampler.py
+++ b/test/python/primitives/test_statevector_sampler.py
@@ -15,7 +15,6 @@
 from __future__ import annotations
 
 import unittest
-from dataclasses import astuple
 
 import numpy as np
 from numpy.typing import NDArray
@@ -573,7 +572,7 @@ def test_circuit_with_multiple_cregs(self):
                 result = sampler.run([qc], shots=self._shots).result()
                 self.assertEqual(len(result), 1)
                 data = result[0].data
-                self.assertEqual(len(astuple(data)), 3)
+                self.assertEqual(len(data), 3)
                 for creg in qc.cregs:
                     self.assertTrue(hasattr(data, creg.name))
                     self._assert_allclose(getattr(data, creg.name), np.array(target[creg.name]))
@@ -606,7 +605,7 @@ def test_circuit_with_aliased_cregs(self):
         result = sampler.run([qc2], shots=self._shots).result()
         self.assertEqual(len(result), 1)
         data = result[0].data
-        self.assertEqual(len(astuple(data)), 3)
+        self.assertEqual(len(data), 3)
         for creg_name in target:
             self.assertTrue(hasattr(data, creg_name))
             self._assert_allclose(getattr(data, creg_name), np.array(target[creg_name]))

From c974c4fb08a14b5303ff05340530035117e03a01 Mon Sep 17 00:00:00 2001
From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Date: Thu, 2 May 2024 19:14:05 +0400
Subject: [PATCH 060/179] Add enhancement to BitArray (#12158)

* Add transpose to BitArray

* add concatenate

* add marginalize and `__getitem__`

* add expectation_value

* add tests of concatenate

* add tests for __getitem__

* add tests for marginalize

* add tests for expectation_value

* make concatenate static method and add stack_shots

* add stack_shots and stack_bits

* Apply suggestions from code review

Co-authored-by: Ian Hincks 

* fix lint

* fix transpose to allow n ints

* Apply suggestions from code review

Co-authored-by: Ian Hincks 

* Apply suggestions from code review

Co-authored-by: Ian Hincks 

* renamed expectation_value method with expectation_values to return all expectation values

* update expectation_values to allow observable array

* fix lint

* Update qiskit/primitives/containers/bit_array.py

Co-authored-by: Ian Hincks 

* updated expectation_values and added slice_shots

* reorg tests and add some more cases

* add reno and improved an error message

* Update qiskit/primitives/containers/bit_array.py

Co-authored-by: Ian Hincks 

* fix a test

---------

Co-authored-by: Ian Hincks 
---
 qiskit/primitives/containers/bit_array.py     | 294 ++++++++++++-
 ...d-bitarray-utilities-c85261138d5a1a97.yaml |  84 ++++
 .../primitives/containers/test_bit_array.py   | 415 +++++++++++++++++-
 3 files changed, 790 insertions(+), 3 deletions(-)
 create mode 100644 releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml

diff --git a/qiskit/primitives/containers/bit_array.py b/qiskit/primitives/containers/bit_array.py
index 308e51f782b8..24d52ca4e85a 100644
--- a/qiskit/primitives/containers/bit_array.py
+++ b/qiskit/primitives/containers/bit_array.py
@@ -19,13 +19,15 @@
 from collections import defaultdict
 from functools import partial
 from itertools import chain, repeat
-from typing import Callable, Iterable, Literal, Mapping
+from typing import Callable, Iterable, Literal, Mapping, Sequence
 
 import numpy as np
 from numpy.typing import NDArray
 
-from qiskit.result import Counts
+from qiskit.exceptions import QiskitError
+from qiskit.result import Counts, sampled_expectation_value
 
+from .observables_array import ObservablesArray, ObservablesArrayLike
 from .shape import ShapedMixin, ShapeInput, shape_tuple
 
 # this lookup table tells you how many bits are 1 in each uint8 value
@@ -37,6 +39,23 @@ def _min_num_bytes(num_bits: int) -> int:
     return num_bits // 8 + (num_bits % 8 > 0)
 
 
+def _unpack(bit_array: BitArray) -> NDArray[np.uint8]:
+    arr = np.unpackbits(bit_array.array, axis=-1, bitorder="big")
+    arr = arr[..., -1 : -bit_array.num_bits - 1 : -1]
+    return arr
+
+
+def _pack(arr: NDArray[np.uint8]) -> tuple[NDArray[np.uint8], int]:
+    arr = arr[..., ::-1]
+    num_bits = arr.shape[-1]
+    pad_size = -num_bits % 8
+    if pad_size > 0:
+        pad_width = [(0, 0)] * (arr.ndim - 1) + [(pad_size, 0)]
+        arr = np.pad(arr, pad_width, constant_values=0)
+    arr = np.packbits(arr, axis=-1, bitorder="big")
+    return arr, num_bits
+
+
 class BitArray(ShapedMixin):
     """Stores an array of bit values.
 
@@ -110,6 +129,13 @@ def __repr__(self):
         desc = f""
         return f"BitArray({desc})"
 
+    def __getitem__(self, indices):
+        if isinstance(indices, tuple) and len(indices) >= self.ndim + 2:
+            raise ValueError(
+                "BitArrays cannot be sliced along the bits axis, see slice_bits() instead."
+            )
+        return BitArray(self._array[indices], self.num_bits)
+
     @property
     def array(self) -> NDArray[np.uint8]:
         """The raw NumPy array of data."""
@@ -347,3 +373,267 @@ def reshape(self, *shape: ShapeInput) -> "BitArray":
         else:
             raise ValueError("Cannot change the size of the array.")
         return BitArray(self._array.reshape(shape), self.num_bits)
+
+    def transpose(self, *axes) -> "BitArray":
+        """Return a bit array with axes transposed.
+
+        Args:
+            axes: None, tuple of ints or n ints. See `ndarray.transpose
+                `_
+                for the details.
+
+        Returns:
+            BitArray: A bit array with axes permuted.
+
+        Raises:
+            ValueError: If ``axes`` don't match this bit array.
+            ValueError: If ``axes`` includes any indices that are out of bounds.
+        """
+        if len(axes) == 0:
+            axes = tuple(reversed(range(self.ndim)))
+        if len(axes) == 1 and isinstance(axes[0], Sequence):
+            axes = axes[0]
+        if len(axes) != self.ndim:
+            raise ValueError("axes don't match bit array")
+        for i in axes:
+            if i >= self.ndim or self.ndim + i < 0:
+                raise ValueError(
+                    f"axis {i} is out of bounds for bit array of dimension {self.ndim}."
+                )
+        axes = tuple(i if i >= 0 else self.ndim + i for i in axes) + (-2, -1)
+        return BitArray(self._array.transpose(axes), self.num_bits)
+
+    def slice_bits(self, indices: int | Sequence[int]) -> "BitArray":
+        """Return a bit array sliced along the bit axis of some indices of interest.
+
+        .. note::
+
+            The convention used by this method is that the index ``0`` corresponds to
+            the least-significant bit in the :attr:`~array`, or equivalently
+            the right-most bitstring entry as returned by
+            :meth:`~get_counts` or :meth:`~get_bitstrings`, etc.
+
+            If this bit array was produced by a sampler, then an index ``i`` corresponds to the
+            :class:`~.ClassicalRegister` location ``creg[i]``.
+
+        Args:
+            indices: The bit positions of interest to slice along.
+
+        Returns:
+            A bit array sliced along the bit axis.
+
+        Raises:
+            ValueError: If there are any invalid indices of the bit axis.
+        """
+        if isinstance(indices, int):
+            indices = (indices,)
+        for index in indices:
+            if index < 0 or index >= self.num_bits:
+                raise ValueError(
+                    f"index {index} is out of bounds for the number of bits {self.num_bits}."
+                )
+        # This implementation introduces a temporary 8x memory overhead due to bit
+        # unpacking. This could be fixed using bitwise functions, at the expense of a
+        # more complicated implementation.
+        arr = _unpack(self)
+        arr = arr[..., indices]
+        arr, num_bits = _pack(arr)
+        return BitArray(arr, num_bits)
+
+    def slice_shots(self, indices: int | Sequence[int]) -> "BitArray":
+        """Return a bit array sliced along the shots axis of some indices of interest.
+
+        Args:
+            indices: The shots positions of interest to slice along.
+
+        Returns:
+            A bit array sliced along the shots axis.
+
+        Raises:
+            ValueError: If there are any invalid indices of the shots axis.
+        """
+        if isinstance(indices, int):
+            indices = (indices,)
+        for index in indices:
+            if index < 0 or index >= self.num_shots:
+                raise ValueError(
+                    f"index {index} is out of bounds for the number of shots {self.num_shots}."
+                )
+        arr = self._array
+        arr = arr[..., indices, :]
+        return BitArray(arr, self.num_bits)
+
+    def expectation_values(self, observables: ObservablesArrayLike) -> NDArray[np.float64]:
+        """Compute the expectation values of the provided observables, broadcasted against
+        this bit array.
+
+        .. note::
+
+            This method returns the real part of the expectation value even if
+            the operator has complex coefficients due to the specification of
+            :func:`~.sampled_expectation_value`.
+
+        Args:
+            observables: The observable(s) to take the expectation value of.
+            Must have a shape broadcastable with with this bit array and
+            the same number of qubits as the number of bits of this bit array.
+            The observables must be diagonal (I, Z, 0 or 1) too.
+
+        Returns:
+            An array of expectation values whose shape is the broadcast shape of ``observables``
+            and this bit array.
+
+        Raises:
+            ValueError: If the provided observables does not have a shape broadcastable with
+                this bit array.
+            ValueError: If the provided observables does not have the same number of qubits as
+                the number of bits of this bit array.
+            ValueError: If the provided observables are not diagonal.
+        """
+        observables = ObservablesArray.coerce(observables)
+        arr_indices = np.fromiter(np.ndindex(self.shape), dtype=object).reshape(self.shape)
+        bc_indices, bc_obs = np.broadcast_arrays(arr_indices, observables)
+        counts = {}
+        arr = np.zeros_like(bc_indices, dtype=float)
+        for index in np.ndindex(bc_indices.shape):
+            loc = bc_indices[index]
+            for pauli, coeff in bc_obs[index].items():
+                if loc not in counts:
+                    counts[loc] = self.get_counts(loc)
+                try:
+                    expval = sampled_expectation_value(counts[loc], pauli)
+                except QiskitError as ex:
+                    raise ValueError(ex.message) from ex
+                arr[index] += expval * coeff
+        return arr
+
+    @staticmethod
+    def concatenate(bit_arrays: Sequence[BitArray], axis: int = 0) -> BitArray:
+        """Join a sequence of bit arrays along an existing axis.
+
+        Args:
+            bit_arrays: The bit arrays must have (1) the same number of bits,
+                (2) the same number of shots, and
+                (3) the same shape, except in the dimension corresponding to axis
+                (the first, by default).
+            axis: The axis along which the arrays will be joined. Default is 0.
+
+        Returns:
+            The concatenated bit array.
+
+        Raises:
+            ValueError: If the sequence of bit arrays is empty.
+            ValueError: If any bit arrays has a different number of bits.
+            ValueError: If any bit arrays has a different number of shots.
+            ValueError: If any bit arrays has a different number of dimensions.
+        """
+        if len(bit_arrays) == 0:
+            raise ValueError("Need at least one bit array to concatenate")
+        num_bits = bit_arrays[0].num_bits
+        num_shots = bit_arrays[0].num_shots
+        ndim = bit_arrays[0].ndim
+        if ndim == 0:
+            raise ValueError("Zero-dimensional bit arrays cannot be concatenated")
+        for i, ba in enumerate(bit_arrays):
+            if ba.num_bits != num_bits:
+                raise ValueError(
+                    "All bit arrays must have same number of bits, "
+                    f"but the bit array at index 0 has {num_bits} bits "
+                    f"and the bit array at index {i} has {ba.num_bits} bits."
+                )
+            if ba.num_shots != num_shots:
+                raise ValueError(
+                    "All bit arrays must have same number of shots, "
+                    f"but the bit array at index 0 has {num_shots} shots "
+                    f"and the bit array at index {i} has {ba.num_shots} shots."
+                )
+            if ba.ndim != ndim:
+                raise ValueError(
+                    "All bit arrays must have same number of dimensions, "
+                    f"but the bit array at index 0 has {ndim} dimension(s) "
+                    f"and the bit array at index {i} has {ba.ndim} dimension(s)."
+                )
+        if axis < 0 or axis >= ndim:
+            raise ValueError(f"axis {axis} is out of bounds for bit array of dimension {ndim}.")
+        data = np.concatenate([ba.array for ba in bit_arrays], axis=axis)
+        return BitArray(data, num_bits)
+
+    @staticmethod
+    def concatenate_shots(bit_arrays: Sequence[BitArray]) -> BitArray:
+        """Join a sequence of bit arrays along the shots axis.
+
+        Args:
+            bit_arrays: The bit arrays must have (1) the same number of bits,
+                and (2) the same shape.
+
+        Returns:
+            The stacked bit array.
+
+        Raises:
+            ValueError: If the sequence of bit arrays is empty.
+            ValueError: If any bit arrays has a different number of bits.
+            ValueError: If any bit arrays has a different shape.
+        """
+        if len(bit_arrays) == 0:
+            raise ValueError("Need at least one bit array to stack")
+        num_bits = bit_arrays[0].num_bits
+        shape = bit_arrays[0].shape
+        for i, ba in enumerate(bit_arrays):
+            if ba.num_bits != num_bits:
+                raise ValueError(
+                    "All bit arrays must have same number of bits, "
+                    f"but the bit array at index 0 has {num_bits} bits "
+                    f"and the bit array at index {i} has {ba.num_bits} bits."
+                )
+            if ba.shape != shape:
+                raise ValueError(
+                    "All bit arrays must have same shape, "
+                    f"but the bit array at index 0 has shape {shape} "
+                    f"and the bit array at index {i} has shape {ba.shape}."
+                )
+        data = np.concatenate([ba.array for ba in bit_arrays], axis=-2)
+        return BitArray(data, num_bits)
+
+    @staticmethod
+    def concatenate_bits(bit_arrays: Sequence[BitArray]) -> BitArray:
+        """Join a sequence of bit arrays along the bits axis.
+
+        .. note::
+            This method is equivalent to per-shot bitstring concatenation.
+
+        Args:
+            bit_arrays: Bit arrays that have (1) the same number of shots,
+                and (2) the same shape.
+
+        Returns:
+            The stacked bit array.
+
+        Raises:
+            ValueError: If the sequence of bit arrays is empty.
+            ValueError: If any bit arrays has a different number of shots.
+            ValueError: If any bit arrays has a different shape.
+        """
+        if len(bit_arrays) == 0:
+            raise ValueError("Need at least one bit array to stack")
+        num_shots = bit_arrays[0].num_shots
+        shape = bit_arrays[0].shape
+        for i, ba in enumerate(bit_arrays):
+            if ba.num_shots != num_shots:
+                raise ValueError(
+                    "All bit arrays must have same number of shots, "
+                    f"but the bit array at index 0 has {num_shots} shots "
+                    f"and the bit array at index {i} has {ba.num_shots} shots."
+                )
+            if ba.shape != shape:
+                raise ValueError(
+                    "All bit arrays must have same shape, "
+                    f"but the bit array at index 0 has shape {shape} "
+                    f"and the bit array at index {i} has shape {ba.shape}."
+                )
+        # This implementation introduces a temporary 8x memory overhead due to bit
+        # unpacking. This could be fixed using bitwise functions, at the expense of a
+        # more complicated implementation.
+        data = np.concatenate([_unpack(ba) for ba in bit_arrays], axis=-1)
+        data, num_bits = _pack(data)
+        return BitArray(data, num_bits)
diff --git a/releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml b/releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml
new file mode 100644
index 000000000000..089a7bcd1133
--- /dev/null
+++ b/releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml
@@ -0,0 +1,84 @@
+---
+features_primitives:
+  - |
+    Added methods to join multiple :class:`~.BitArray` objects along various axes.
+
+    - :meth:`~.BitArray.concatenate`: join arrays along an existing axis of the arrays.
+    - :meth:`~.BitArray.concatenate_bits`: join arrays along the bit axis.
+    - :meth:`~.BitArray.concatenate_shots`: join arrays along the shots axis.
+
+    .. code-block::
+
+      ba = BitArray.from_samples(['00', '11'])
+      print(ba)
+      # BitArray()
+
+      # reshape the bit array because `concatenate` requires an axis.
+      ba_ = ba.reshape(1, 2)
+      print(ba_)
+      # BitArray()
+
+      ba2 = BitArray.concatenate([ba_, ba_])
+      print(ba2.get_bitstrings())
+      # ['00', '11', '00', '11']
+
+      # `concatenate_bits` and `concatenates_shots` do not require any axis.
+
+      ba3 = BitArray.concatenate_bits([ba, ba])
+      print(ba3.get_bitstrings())
+      # ['0000', '1111']
+
+      ba4 = BitArray.concatenate_shots([ba, ba])
+      print(ba4.get_bitstrings())
+      # ['00', '11', '00', '11']
+
+  - |
+    Added methods to generate a subset of :class:`~.BitArray` object by slicing along various axes.
+
+    - :meth:`~.BitArray.__getitem__`: slice the array along an existing axis of the array.
+    - :meth:`~.BitArray.slice_bits`: slice the array along the bit axis.
+    - :meth:`~.BitArray.slice_shots`: slice the array along the shot axis.
+
+    .. code-block::
+
+      ba = BitArray.from_samples(['0000', '0001', '0010', '0011'], 4)
+      print(ba)
+      # BitArray()
+      print(ba.get_bitstrings())
+      # ['0000', '0001', '0010', '0011']
+
+      ba2 = ba.reshape(2, 2)
+      print(ba2)
+      # BitArray()
+      print(ba2[0].get_bitstrings())
+      # ['0000', '0001']
+      print(ba2[1].get_bitstrings())
+      # ['0010', '0011']
+
+      ba3 = ba.slice_bits([0, 2])
+      print(ba3.get_bitstrings())
+      # ['00', '01', '00', '01']
+
+      ba4 = ba.slice_shots([0, 2])
+      print(ba3.get_bitstrings())
+      # ['0000', '0010']
+
+  - |
+    Added a method :meth:`~.BitArray.transpose` to transpose a :class:`~.BitArray`.
+
+    .. code-block::
+
+      ba = BitArray.from_samples(['00', '11']).reshape(2, 1, 1)
+      print(ba)
+      # BitArray()
+      print(ba.transpose())
+      # BitArray()
+
+  - |
+    Added a method :meth:`~.BitArray.expectation_values` to compute expectation values of diagonal operators.
+
+    .. code-block::
+
+      ba = BitArray.from_samples(['01', '11'])
+      print(ba.expectation_values(["IZ", "ZI", "01"]))
+      # [-1.   0.   0.5]
diff --git a/test/python/primitives/containers/test_bit_array.py b/test/python/primitives/containers/test_bit_array.py
index a85118e27eab..69f02fd46daa 100644
--- a/test/python/primitives/containers/test_bit_array.py
+++ b/test/python/primitives/containers/test_bit_array.py
@@ -13,13 +13,14 @@
 """Unit tests for BitArray."""
 
 from itertools import product
+from test import QiskitTestCase
 
 import ddt
 import numpy as np
 
 from qiskit.primitives.containers import BitArray
+from qiskit.quantum_info import Pauli, SparsePauliOp
 from qiskit.result import Counts
-from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
 def u_8(arr):
@@ -282,3 +283,415 @@ def test_reshape(self):
         self.assertEqual(ba.reshape(360 * 2, 16).shape, (720,))
         self.assertEqual(ba.reshape(360 * 2, 16).num_shots, 16)
         self.assertEqual(ba.reshape(360 * 2, 16).num_bits, 15)
+
+    def test_transpose(self):
+        """Test the transpose method."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        # Since the input dtype is uint16, bit array requires at least two u8.
+        # Thus, 9 is the minimum number of qubits, i.e., 8 + 1.
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+
+        with self.subTest("default arg"):
+            ba2 = ba.transpose()
+            self.assertEqual(ba2.shape, (3, 2, 1))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((k, j, i)))
+
+        with self.subTest("tuple 1"):
+            ba2 = ba.transpose((2, 1, 0))
+            self.assertEqual(ba2.shape, (3, 2, 1))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((k, j, i)))
+
+        with self.subTest("tuple 2"):
+            ba2 = ba.transpose((0, 1, 2))
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, j, k)))
+
+        with self.subTest("tuple 3"):
+            ba2 = ba.transpose((0, 2, 1))
+            self.assertEqual(ba2.shape, (1, 3, 2))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, k, j)))
+
+        with self.subTest("tuple, negative indices"):
+            ba2 = ba.transpose((0, -1, -2))
+            self.assertEqual(ba2.shape, (1, 3, 2))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, k, j)))
+
+        with self.subTest("ints"):
+            ba2 = ba.transpose(2, 1, 0)
+            self.assertEqual(ba2.shape, (3, 2, 1))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((k, j, i)))
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "axes don't match bit array"):
+                _ = ba.transpose((0, 1))
+            with self.assertRaisesRegex(ValueError, "axes don't match bit array"):
+                _ = ba.transpose((0, 1, 2, 3))
+            with self.assertRaisesRegex(ValueError, "axis [0-9]+ is out of bounds for bit array"):
+                _ = ba.transpose((0, 1, 4))
+            with self.assertRaisesRegex(ValueError, "axis -[0-9]+ is out of bounds for bit array"):
+                _ = ba.transpose((0, 1, -4))
+            with self.assertRaisesRegex(ValueError, "repeated axis in transpose"):
+                _ = ba.transpose((0, 1, 1))
+
+    def test_concatenate(self):
+        """Test the concatenate function."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+        concatenate = BitArray.concatenate
+
+        with self.subTest("2 arrays, default"):
+            ba2 = concatenate([ba, ba])
+            self.assertEqual(ba2.shape, (2, 2, 3))
+            for j, k in product(range(2), range(3)):
+                self.assertEqual(ba2.get_counts((0, j, k)), ba2.get_counts((1, j, k)))
+
+        with self.subTest("2 arrays, axis"):
+            ba2 = concatenate([ba, ba], axis=1)
+            self.assertEqual(ba2.shape, (1, 4, 3))
+            for j, k in product(range(2), range(3)):
+                self.assertEqual(ba2.get_counts((0, j, k)), ba2.get_counts((0, j + 2, k)))
+
+        with self.subTest("3 arrays"):
+            ba2 = concatenate([ba, ba, ba])
+            self.assertEqual(ba2.shape, (3, 2, 3))
+            for j, k in product(range(2), range(3)):
+                self.assertEqual(ba2.get_counts((0, j, k)), ba2.get_counts((1, j, k)))
+                self.assertEqual(ba2.get_counts((1, j, k)), ba2.get_counts((2, j, k)))
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "Need at least one bit array to concatenate"):
+                _ = concatenate([])
+            with self.assertRaisesRegex(ValueError, "axis -1 is out of bounds"):
+                _ = concatenate([ba, ba], -1)
+            with self.assertRaisesRegex(ValueError, "axis 100 is out of bounds"):
+                _ = concatenate([ba, ba], 100)
+
+            ba2 = BitArray(data, 10)
+            with self.assertRaisesRegex(ValueError, "All bit arrays must have same number of bits"):
+                _ = concatenate([ba, ba2])
+
+            data2 = np.frombuffer(np.arange(30, dtype=np.uint16).tobytes(), dtype=np.uint8)
+            data2 = data2.reshape(1, 2, 3, 5, 2)[..., ::-1]
+            ba2 = BitArray(data2, 9)
+            with self.assertRaisesRegex(
+                ValueError, "All bit arrays must have same number of shots"
+            ):
+                _ = concatenate([ba, ba2])
+
+            ba2 = ba.reshape(2, 3)
+            with self.assertRaisesRegex(
+                ValueError, "All bit arrays must have same number of dimensions"
+            ):
+                _ = concatenate([ba, ba2])
+
+    def test_concatenate_shots(self):
+        """Test the concatenate_shots function."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+        concatenate_shots = BitArray.concatenate_shots
+
+        with self.subTest("2 arrays"):
+            ba2 = concatenate_shots([ba, ba])
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            self.assertEqual(ba2.num_bits, 9)
+            self.assertEqual(ba2.num_shots, 2 * ba.num_shots)
+            for i, j, k in product(range(1), range(2), range(3)):
+                expected = {key: val * 2 for key, val in ba.get_counts((i, j, k)).items()}
+                counts2 = ba2.get_counts((i, j, k))
+                self.assertEqual(counts2, expected)
+
+        with self.subTest("3 arrays"):
+            ba2 = concatenate_shots([ba, ba, ba])
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            self.assertEqual(ba2.num_bits, 9)
+            self.assertEqual(ba2.num_shots, 3 * ba.num_shots)
+            for i, j, k in product(range(1), range(2), range(3)):
+                expected = {key: val * 3 for key, val in ba.get_counts((i, j, k)).items()}
+                counts2 = ba2.get_counts((i, j, k))
+                self.assertEqual(counts2, expected)
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "Need at least one bit array to stack"):
+                _ = concatenate_shots([])
+
+            ba2 = BitArray(data, 10)
+            with self.assertRaisesRegex(ValueError, "All bit arrays must have same number of bits"):
+                _ = concatenate_shots([ba, ba2])
+
+            ba2 = ba.reshape(2, 3)
+            with self.assertRaisesRegex(ValueError, "All bit arrays must have same shape"):
+                _ = concatenate_shots([ba, ba2])
+
+    def test_concatenate_bits(self):
+        """Test the concatenate_bits function."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+        concatenate_bits = BitArray.concatenate_bits
+
+        with self.subTest("2 arrays"):
+            ba_01 = ba.slice_bits([0, 1])
+            ba2 = concatenate_bits([ba, ba_01])
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            self.assertEqual(ba2.num_bits, 11)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            for i, j, k in product(range(1), range(2), range(3)):
+                bs = ba.get_bitstrings((i, j, k))
+                bs_01 = ba_01.get_bitstrings((i, j, k))
+                expected = [s1 + s2 for s1, s2 in zip(bs_01, bs)]
+                bs2 = ba2.get_bitstrings((i, j, k))
+                self.assertEqual(bs2, expected)
+
+        with self.subTest("3 arrays"):
+            ba_01 = ba.slice_bits([0, 1])
+            ba2 = concatenate_bits([ba, ba_01, ba_01])
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            self.assertEqual(ba2.num_bits, 13)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            for i, j, k in product(range(1), range(2), range(3)):
+                bs = ba.get_bitstrings((i, j, k))
+                bs_01 = ba_01.get_bitstrings((i, j, k))
+                expected = [s1 + s1 + s2 for s1, s2 in zip(bs_01, bs)]
+                bs2 = ba2.get_bitstrings((i, j, k))
+                self.assertEqual(bs2, expected)
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "Need at least one bit array to stack"):
+                _ = concatenate_bits([])
+
+            data2 = np.frombuffer(np.arange(30, dtype=np.uint16).tobytes(), dtype=np.uint8)
+            data2 = data2.reshape(1, 2, 3, 5, 2)[..., ::-1]
+            ba2 = BitArray(data2, 9)
+            with self.assertRaisesRegex(
+                ValueError, "All bit arrays must have same number of shots"
+            ):
+                _ = concatenate_bits([ba, ba2])
+
+            ba2 = ba.reshape(2, 3)
+            with self.assertRaisesRegex(ValueError, "All bit arrays must have same shape"):
+                _ = concatenate_bits([ba, ba2])
+
+    def test_getitem(self):
+        """Test the __getitem__ method."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+
+        with self.subTest("all"):
+            ba2 = ba[:]
+            self.assertEqual(ba2.shape, (1, 2, 3))
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, j, k)))
+
+        with self.subTest("no slice"):
+            ba2 = ba[0, 1, 2]
+            self.assertEqual(ba2.shape, ())
+            self.assertEqual(ba.get_counts((0, 1, 2)), ba2.get_counts())
+
+        with self.subTest("slice"):
+            ba2 = ba[0, :, 2]
+            self.assertEqual(ba2.shape, (2,))
+            for j in range(2):
+                self.assertEqual(ba.get_counts((0, j, 2)), ba2.get_counts(j))
+
+    def test_slice_bits(self):
+        """Test the slice_bits method."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+
+        with self.subTest("all"):
+            ba2 = ba.slice_bits(range(ba.num_bits))
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            self.assertEqual(ba2.num_bits, ba.num_bits)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, j, k)))
+
+        with self.subTest("1 bit, int"):
+            ba2 = ba.slice_bits(0)
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            self.assertEqual(ba2.num_bits, 1)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba2.get_counts((i, j, k)), {"0": 5, "1": 5})
+
+        with self.subTest("1 bit, list"):
+            ba2 = ba.slice_bits([0])
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            self.assertEqual(ba2.num_bits, 1)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba2.get_counts((i, j, k)), {"0": 5, "1": 5})
+
+        with self.subTest("2 bits"):
+            ba2 = ba.slice_bits([0, 1])
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            self.assertEqual(ba2.num_bits, 2)
+            even = {"00": 3, "01": 3, "10": 2, "11": 2}
+            odd = {"10": 3, "11": 3, "00": 2, "01": 2}
+            for count, (i, j, k) in enumerate(product(range(1), range(2), range(3))):
+                expect = even if count % 2 == 0 else odd
+                self.assertEqual(ba2.get_counts((i, j, k)), expect)
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"):
+                _ = ba.slice_bits(-1)
+            with self.assertRaisesRegex(ValueError, "index 9 is out of bounds"):
+                _ = ba.slice_bits(9)
+
+    def test_slice_shots(self):
+        """Test the slice_shots method."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+
+        with self.subTest("all"):
+            ba2 = ba.slice_shots(range(ba.num_shots))
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_bits, ba.num_bits)
+            self.assertEqual(ba2.num_shots, ba.num_shots)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba.get_counts((i, j, k)), ba2.get_counts((i, j, k)))
+
+        with self.subTest("1 shot, int"):
+            ba2 = ba.slice_shots(0)
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_bits, ba.num_bits)
+            self.assertEqual(ba2.num_shots, 1)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba2.get_bitstrings((i, j, k)), [ba.get_bitstrings((i, j, k))[0]])
+
+        with self.subTest("1 shot, list"):
+            ba2 = ba.slice_shots([0])
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_bits, ba.num_bits)
+            self.assertEqual(ba2.num_shots, 1)
+            for i, j, k in product(range(1), range(2), range(3)):
+                self.assertEqual(ba2.get_bitstrings((i, j, k)), [ba.get_bitstrings((i, j, k))[0]])
+
+        with self.subTest("multiple shots"):
+            indices = [1, 2, 3, 5, 8]
+            ba2 = ba.slice_shots(indices)
+            self.assertEqual(ba2.shape, ba.shape)
+            self.assertEqual(ba2.num_bits, ba.num_bits)
+            self.assertEqual(ba2.num_shots, len(indices))
+            for i, j, k in product(range(1), range(2), range(3)):
+                expected = [
+                    bs for ind, bs in enumerate(ba.get_bitstrings((i, j, k))) if ind in indices
+                ]
+                self.assertEqual(ba2.get_bitstrings((i, j, k)), expected)
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "index -1 is out of bounds"):
+                _ = ba.slice_shots(-1)
+            with self.assertRaisesRegex(ValueError, "index 10 is out of bounds"):
+                _ = ba.slice_shots(10)
+
+    def test_expectation_values(self):
+        """Test the expectation_values method."""
+        # this creates incrementing bitstrings from 0 to 59
+        data = np.frombuffer(np.arange(60, dtype=np.uint16).tobytes(), dtype=np.uint8)
+        data = data.reshape(1, 2, 3, 10, 2)[..., ::-1]
+        ba = BitArray(data, 9)
+        self.assertEqual(ba.shape, (1, 2, 3))
+        op = "I" * 8 + "Z"
+        op2 = "I" * 8 + "0"
+        op3 = "I" * 8 + "1"
+        pauli = Pauli(op)
+        sp_op = SparsePauliOp(op)
+        sp_op2 = SparsePauliOp.from_sparse_list([("Z", [6], 1)], num_qubits=9)
+
+        with self.subTest("str"):
+            expval = ba.expectation_values(op)
+            # both 0 and 1 appear 5 times
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.zeros((ba.shape)))
+
+            expval = ba.expectation_values(op2)
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.full((ba.shape), 0.5))
+
+            expval = ba.expectation_values(op3)
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.full((ba.shape), 0.5))
+
+            ba2 = ba.slice_bits(6)
+            # 6th bit are all 0
+            expval = ba2.expectation_values("Z")
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.ones(ba.shape))
+
+            ba3 = ba.slice_bits(5)
+            # 5th bit distributes as follows.
+            # (0, 0, 0) {'0': 10}
+            # (0, 0, 1) {'0': 10}
+            # (0, 0, 2) {'0': 10}
+            # (0, 1, 0) {'0': 2, '1': 8}
+            # (0, 1, 1) {'1': 10}
+            # (0, 1, 2) {'1': 10}
+            expval = ba3.expectation_values("0")
+            expected = np.array([[[1, 1, 1], [0.2, 0, 0]]])
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, expected)
+
+        with self.subTest("Pauli"):
+            expval = ba.expectation_values(pauli)
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.zeros((ba.shape)))
+
+        with self.subTest("SparsePauliOp"):
+            expval = ba.expectation_values(sp_op)
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.zeros((ba.shape)))
+
+            expval = ba.expectation_values(sp_op2)
+            # 6th bit are all 0
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, np.ones((ba.shape)))
+
+        with self.subTest("ObservableArray"):
+            obs = ["Z", "0", "1"]
+            ba2 = ba.slice_bits(5)
+            expval = ba2.expectation_values(obs)
+            expected = np.array([[[1, 1, 0], [-0.6, 0, 1]]])
+            self.assertEqual(expval.shape, ba.shape)
+            np.testing.assert_allclose(expval, expected)
+
+            ba4 = BitArray.from_counts([{0: 1}, {1: 1}]).reshape(2, 1)
+            expval = ba4.expectation_values(obs)
+            expected = np.array([[1, 1, 0], [-1, 0, 1]])
+            self.assertEqual(expval.shape, (2, 3))
+            np.testing.assert_allclose(expval, expected)
+
+        with self.subTest("errors"):
+            with self.assertRaisesRegex(ValueError, "shape mismatch"):
+                _ = ba.expectation_values([op, op2])
+            with self.assertRaisesRegex(ValueError, "One or more operators not same length"):
+                _ = ba.expectation_values("Z")
+            with self.assertRaisesRegex(ValueError, "is not diagonal"):
+                _ = ba.expectation_values("X" * ba.num_bits)

From f34fb21219077311eb0e3f35e732c50ba4eb4101 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 2 May 2024 13:25:15 -0400
Subject: [PATCH 061/179] Add star to linear pre-routing pass (#11387)

* Add Star to linear pre-routing pass

This commit adds a new transpiler pass StarPreRouting that adds a
dedicated transformation that finds a star graph connectivity subcircuit
and replaces it with a linear routing equivalent. This makes the circuit
easier for a routing pass to work with.

* Add missing doc imports

* Fix handling of 1q gates and swap tracking

This commit fixes two related issues. The first is that it fixed the
handling of 1q gates in the middle of a star sequence. The sequence
collection was correctly skipping the 1q gates, but when we reassembled
the circuit and applied the linear routing it would incorrectly build
the routing without the 1q gates. This would result in the 1q gates
being pushed after the star. At the same time the other related issue
was that the permutations caused by the swap insertions were not taken
into account for subsequent gates. This would result in gates being
placed on the wrong bits and incorrect results being returned.

* fixed/tested star-prerouting

* Apply suggestions from code review

* changed moved self.dag to a function parameter

* Removing the usage of FinalizeLayout in star prerouting tests

* format

---------

Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
---
 qiskit/transpiler/passes/__init__.py          |   2 +
 qiskit/transpiler/passes/routing/__init__.py  |   1 +
 .../passes/routing/star_prerouting.py         | 415 +++++++++++++++
 .../star-prerouting-0998b59880c20cef.yaml     |  32 ++
 .../python/transpiler/test_star_prerouting.py | 484 ++++++++++++++++++
 5 files changed, 934 insertions(+)
 create mode 100644 qiskit/transpiler/passes/routing/star_prerouting.py
 create mode 100644 releasenotes/notes/star-prerouting-0998b59880c20cef.yaml
 create mode 100644 test/python/transpiler/test_star_prerouting.py

diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py
index c1e1705f1cce..54599e00b9ae 100644
--- a/qiskit/transpiler/passes/__init__.py
+++ b/qiskit/transpiler/passes/__init__.py
@@ -46,6 +46,7 @@
    StochasticSwap
    SabreSwap
    Commuting2qGateRouter
+   StarPreRouting
 
 Basis Change
 ============
@@ -205,6 +206,7 @@
 from .routing import StochasticSwap
 from .routing import SabreSwap
 from .routing import Commuting2qGateRouter
+from .routing import StarPreRouting
 
 # basis change
 from .basis import Decompose
diff --git a/qiskit/transpiler/passes/routing/__init__.py b/qiskit/transpiler/passes/routing/__init__.py
index 2316705b4a1a..a1ac25fb4145 100644
--- a/qiskit/transpiler/passes/routing/__init__.py
+++ b/qiskit/transpiler/passes/routing/__init__.py
@@ -19,3 +19,4 @@
 from .sabre_swap import SabreSwap
 from .commuting_2q_gate_routing.commuting_2q_gate_router import Commuting2qGateRouter
 from .commuting_2q_gate_routing.swap_strategy import SwapStrategy
+from .star_prerouting import StarPreRouting
diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py
new file mode 100644
index 000000000000..8e278471295a
--- /dev/null
+++ b/qiskit/transpiler/passes/routing/star_prerouting.py
@@ -0,0 +1,415 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2023.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Search for star connectivity patterns and replace them with."""
+from typing import Iterable, Union, Optional, List, Tuple
+from math import floor, log10
+
+from qiskit.circuit import Barrier
+from qiskit.dagcircuit import DAGOpNode, DAGDepNode, DAGDependency, DAGCircuit
+from qiskit.transpiler import Layout
+from qiskit.transpiler.basepasses import TransformationPass
+from qiskit.circuit.library import SwapGate
+
+
+class StarBlock:
+    """Defines blocks representing star-shaped pieces of a circuit."""
+
+    def __init__(self, nodes=None, center=None, num2q=0):
+        self.center = center
+        self.num2q = num2q
+        self.nodes = [] if nodes is None else nodes
+
+    def get_nodes(self):
+        """Returns the list of nodes used in the block."""
+        return self.nodes
+
+    def append_node(self, node):
+        """
+        If node can be added to block while keeping the block star-shaped, and
+        return True. Otherwise, does not add node to block and returns False.
+        """
+
+        added = False
+
+        if len(node.qargs) == 1:
+            self.nodes.append(node)
+            added = True
+        elif self.center is None:
+            self.center = set(node.qargs)
+            self.nodes.append(node)
+            self.num2q += 1
+            added = True
+        elif isinstance(self.center, set):
+            if node.qargs[0] in self.center:
+                self.center = node.qargs[0]
+                self.nodes.append(node)
+                self.num2q += 1
+                added = True
+            elif node.qargs[1] in self.center:
+                self.center = node.qargs[1]
+                self.nodes.append(node)
+                self.num2q += 1
+                added = True
+        else:
+            if self.center in node.qargs:
+                self.nodes.append(node)
+                self.num2q += 1
+                added = True
+
+        return added
+
+    def size(self):
+        """
+        Returns the number of two-qubit quantum gates in this block.
+        """
+        return self.num2q
+
+
+class StarPreRouting(TransformationPass):
+    """Run star to linear pre-routing
+
+    This pass is a logical optimization pass that rewrites any
+    solely 2q gate star connectivity subcircuit as a linear connectivity
+    equivalent with swaps.
+
+    For example:
+
+      .. plot::
+         :include-source:
+
+         from qiskit.circuit import QuantumCircuit
+         from qiskit.transpiler.passes import StarPreRouting
+
+         qc = QuantumCircuit(10)
+         qc.h(0)
+         qc.cx(0, range(1, 5))
+         qc.h(9)
+         qc.cx(9, range(8, 4, -1))
+         qc.measure_all()
+         StarPreRouting()(qc).draw("mpl")
+
+    This pass was inspired by a similar pass described in Section IV of:
+    C. Campbell et al., "Superstaq: Deep Optimization of Quantum Programs,"
+    2023 IEEE International Conference on Quantum Computing and Engineering (QCE),
+    Bellevue, WA, USA, 2023, pp. 1020-1032, doi: 10.1109/QCE57702.2023.00116.
+    """
+
+    def __init__(self):
+        """StarPreRouting"""
+
+        self._pending_nodes: Optional[list[Union[DAGOpNode, DAGDepNode]]] = None
+        self._in_degree: Optional[dict[Union[DAGOpNode, DAGDepNode], int]] = None
+        super().__init__()
+
+    def _setup_in_degrees(self, dag):
+        """For an efficient implementation, for every node we keep the number of its
+        unprocessed immediate predecessors (called ``_in_degree``). This ``_in_degree``
+        is set up at the start and updated throughout the algorithm.
+        A node is leaf (or input) node iff its ``_in_degree`` is 0.
+        When a node is (marked as) collected, the ``_in_degree`` of each of its immediate
+        successor is updated by subtracting 1.
+        Additionally, ``_pending_nodes`` explicitly keeps the list of nodes whose
+        ``_in_degree`` is 0.
+        """
+        self._pending_nodes = []
+        self._in_degree = {}
+        for node in self._op_nodes(dag):
+            deg = len(self._direct_preds(dag, node))
+            self._in_degree[node] = deg
+            if deg == 0:
+                self._pending_nodes.append(node)
+
+    def _op_nodes(self, dag) -> Iterable[Union[DAGOpNode, DAGDepNode]]:
+        """Returns DAG nodes."""
+        if not isinstance(dag, DAGDependency):
+            return dag.op_nodes()
+        else:
+            return dag.get_nodes()
+
+    def _direct_preds(self, dag, node):
+        """Returns direct predecessors of a node. This function takes into account the
+        direction of collecting blocks, that is node's predecessors when collecting
+        backwards are the direct successors of a node in the DAG.
+        """
+        if not isinstance(dag, DAGDependency):
+            return [pred for pred in dag.predecessors(node) if isinstance(pred, DAGOpNode)]
+        else:
+            return [dag.get_node(pred_id) for pred_id in dag.direct_predecessors(node.node_id)]
+
+    def _direct_succs(self, dag, node):
+        """Returns direct successors of a node. This function takes into account the
+        direction of collecting blocks, that is node's successors when collecting
+        backwards are the direct predecessors of a node in the DAG.
+        """
+        if not isinstance(dag, DAGDependency):
+            return [succ for succ in dag.successors(node) if isinstance(succ, DAGOpNode)]
+        else:
+            return [dag.get_node(succ_id) for succ_id in dag.direct_successors(node.node_id)]
+
+    def _have_uncollected_nodes(self):
+        """Returns whether there are uncollected (pending) nodes"""
+        return len(self._pending_nodes) > 0
+
+    def collect_matching_block(self, dag, filter_fn):
+        """Iteratively collects the largest block of input nodes (that is, nodes with
+        ``_in_degree`` equal to 0) that match a given filtering function.
+        Examples of this include collecting blocks of swap gates,
+        blocks of linear gates (CXs and SWAPs), blocks of Clifford gates, blocks of single-qubit gates,
+        blocks of two-qubit gates, etc.  Here 'iteratively' means that once a node is collected,
+        the ``_in_degree`` of each of its immediate successor is decreased by 1, allowing more nodes
+        to become input and to be eligible for collecting into the current block.
+        Returns the block of collected nodes.
+        """
+        unprocessed_pending_nodes = self._pending_nodes
+        self._pending_nodes = []
+
+        current_block = StarBlock()
+
+        # Iteratively process unprocessed_pending_nodes:
+        # - any node that does not match filter_fn is added to pending_nodes
+        # - any node that match filter_fn is added to the current_block,
+        #   and some of its successors may be moved to unprocessed_pending_nodes.
+        while unprocessed_pending_nodes:
+            new_pending_nodes = []
+            for node in unprocessed_pending_nodes:
+                added = filter_fn(node) and current_block.append_node(node)
+                if added:
+                    # update the _in_degree of node's successors
+                    for suc in self._direct_succs(dag, node):
+                        self._in_degree[suc] -= 1
+                        if self._in_degree[suc] == 0:
+                            new_pending_nodes.append(suc)
+                else:
+                    self._pending_nodes.append(node)
+            unprocessed_pending_nodes = new_pending_nodes
+
+        return current_block
+
+    def collect_all_matching_blocks(
+        self,
+        dag,
+        min_block_size=2,
+    ):
+        """Collects all blocks that match a given filtering function filter_fn.
+        This iteratively finds the largest block that does not match filter_fn,
+        then the largest block that matches filter_fn, and so on, until no more uncollected
+        nodes remain. Intuitively, finding larger blocks of non-matching nodes helps to
+        find larger blocks of matching nodes later on. The option ``min_block_size``
+        specifies the minimum number of gates in the block for the block to be collected.
+
+        By default, blocks are collected in the direction from the inputs towards the outputs
+        of the circuit. The option ``collect_from_back`` allows to change this direction,
+        that is collect blocks from the outputs towards the inputs of the circuit.
+
+        Returns the list of matching blocks only.
+        """
+
+        def filter_fn(node):
+            """Specifies which nodes can be collected into star blocks."""
+            return (
+                len(node.qargs) <= 2
+                and len(node.cargs) == 0
+                and getattr(node.op, "condition", None) is None
+                and not isinstance(node.op, Barrier)
+            )
+
+        def not_filter_fn(node):
+            """Returns the opposite of filter_fn."""
+            return not filter_fn(node)
+
+        # Note: the collection direction must be specified before setting in-degrees
+        self._setup_in_degrees(dag)
+
+        # Iteratively collect non-matching and matching blocks.
+        matching_blocks: list[StarBlock] = []
+        processing_order = []
+        while self._have_uncollected_nodes():
+            self.collect_matching_block(dag, filter_fn=not_filter_fn)
+            matching_block = self.collect_matching_block(dag, filter_fn=filter_fn)
+            if matching_block.size() >= min_block_size:
+                matching_blocks.append(matching_block)
+            processing_order.append(matching_block)
+
+        processing_order = [n for p in processing_order for n in p.nodes]
+
+        return matching_blocks, processing_order
+
+    def run(self, dag):
+        # Extract StarBlocks from DAGCircuit / DAGDependency / DAGDependencyV2
+        star_blocks, processing_order = self.determine_star_blocks_processing(dag, min_block_size=2)
+
+        if not star_blocks:
+            return dag
+
+        if all(b.size() < 3 for b in star_blocks):
+            # we only process blocks with less than 3 two-qubit gates in this pre-routing pass
+            # if they occur in a collection of larger stars, otherwise we consider them to be 'lines'
+            return dag
+
+        # Create a new DAGCircuit / DAGDependency / DAGDependencyV2, replacing each
+        # star block by a linear sequence of gates
+        new_dag, qubit_mapping = self.star_preroute(dag, star_blocks, processing_order)
+
+        # Fix output permuation -- 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_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
+        if current_layout := self.property_set["virtual_permutation_layout"] is not None:
+            self.property_set["virtual_permutation_layout"] = current_layout.compose(new_layout)
+        else:
+            self.property_set["virtual_permutation_layout"] = new_layout
+
+        return new_dag
+
+    def determine_star_blocks_processing(
+        self, dag: Union[DAGCircuit, DAGDependency], min_block_size: int
+    ) -> Tuple[List[StarBlock], Union[List[DAGOpNode], List[DAGDepNode]]]:
+        """Returns star blocks in dag and the processing order of nodes within these star blocks
+        Args:
+            dag (DAGCircuit or DAGDependency): a dag on which star blocks should be determined.
+            min_block_size (int): minimum number of two-qubit gates in a star block.
+
+        Returns:
+            List[StarBlock]: a list of star blocks in the given dag
+            Union[List[DAGOpNode], List[DAGDepNode]]: a list of operations specifying processing order
+        """
+        blocks, processing_order = self.collect_all_matching_blocks(
+            dag, min_block_size=min_block_size
+        )
+        return blocks, processing_order
+
+    def star_preroute(self, dag, blocks, processing_order):
+        """Returns star blocks in dag and the processing order of nodes within these star blocks
+        Args:
+            dag (DAGCircuit or DAGDependency): a dag on which star prerouting should be performed.
+            blocks (List[StarBlock]): a list of star blocks in the given dag.
+            processing_order (Union[List[DAGOpNode], List[DAGDepNode]]): a list of operations specifying
+            processing order
+
+        Returns:
+            new_dag: a dag specifying the pre-routed circuit
+            qubit_mapping: the final qubit mapping after pre-routing
+        """
+        node_to_block_id = {}
+        for i, block in enumerate(blocks):
+            for node in block.get_nodes():
+                node_to_block_id[node] = i
+
+        new_dag = dag.copy_empty_like()
+        processed_block_ids = set()
+        qubit_mapping = list(range(len(dag.qubits)))
+
+        def _apply_mapping(qargs, qubit_mapping, qubits):
+            return tuple(qubits[qubit_mapping[dag.find_bit(qubit).index]] for qubit in qargs)
+
+        is_first_star = True
+        last_2q_gate = [
+            op
+            for op in reversed(processing_order)
+            if ((len(op.qargs) > 1) and (op.name != "barrier"))
+        ]
+        if len(last_2q_gate) > 0:
+            last_2q_gate = last_2q_gate[0]
+        else:
+            last_2q_gate = None
+
+        int_digits = floor(log10(len(processing_order))) + 1
+        processing_order_s = set(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
+
+        for node in dag.topological_op_nodes(key=tie_breaker_key):
+            block_id = node_to_block_id.get(node, None)
+            if block_id is not None:
+                if block_id in processed_block_ids:
+                    continue
+
+                processed_block_ids.add(block_id)
+
+                # process the whole block
+                block = blocks[block_id]
+                sequence = block.nodes
+                center_node = block.center
+
+                if len(sequence) == 2:
+                    for inner_node in sequence:
+                        new_dag.apply_operation_back(
+                            inner_node.op,
+                            _apply_mapping(inner_node.qargs, qubit_mapping, dag.qubits),
+                            inner_node.cargs,
+                            check=False,
+                        )
+                    continue
+                swap_source = None
+                prev = None
+                for inner_node in sequence:
+                    if (len(inner_node.qargs) == 1) or (inner_node.qargs == prev):
+                        new_dag.apply_operation_back(
+                            inner_node.op,
+                            _apply_mapping(inner_node.qargs, qubit_mapping, dag.qubits),
+                            inner_node.cargs,
+                            check=False,
+                        )
+                        continue
+                    if is_first_star and swap_source is None:
+                        swap_source = center_node
+                        new_dag.apply_operation_back(
+                            inner_node.op,
+                            _apply_mapping(inner_node.qargs, qubit_mapping, dag.qubits),
+                            inner_node.cargs,
+                            check=False,
+                        )
+
+                        prev = inner_node.qargs
+                        continue
+                    # place 2q-gate and subsequent swap gate
+                    new_dag.apply_operation_back(
+                        inner_node.op,
+                        _apply_mapping(inner_node.qargs, qubit_mapping, dag.qubits),
+                        inner_node.cargs,
+                        check=False,
+                    )
+
+                    if not inner_node is last_2q_gate and not isinstance(inner_node.op, Barrier):
+                        new_dag.apply_operation_back(
+                            SwapGate(),
+                            _apply_mapping(inner_node.qargs, qubit_mapping, dag.qubits),
+                            inner_node.cargs,
+                            check=False,
+                        )
+                        # Swap mapping
+                        index_0 = dag.find_bit(inner_node.qargs[0]).index
+                        index_1 = dag.find_bit(inner_node.qargs[1]).index
+                        qubit_mapping[index_1], qubit_mapping[index_0] = (
+                            qubit_mapping[index_0],
+                            qubit_mapping[index_1],
+                        )
+
+                    prev = inner_node.qargs
+                is_first_star = False
+            else:
+                # the node is not part of a block
+                new_dag.apply_operation_back(
+                    node.op,
+                    _apply_mapping(node.qargs, qubit_mapping, dag.qubits),
+                    node.cargs,
+                    check=False,
+                )
+        return new_dag, qubit_mapping
diff --git a/releasenotes/notes/star-prerouting-0998b59880c20cef.yaml b/releasenotes/notes/star-prerouting-0998b59880c20cef.yaml
new file mode 100644
index 000000000000..0bf60329a230
--- /dev/null
+++ b/releasenotes/notes/star-prerouting-0998b59880c20cef.yaml
@@ -0,0 +1,32 @@
+---
+features:
+  - |
+    Added a new transpiler pass :class:`.StarPreRouting` which is designed to identify star connectivity subcircuits
+    and then replace them with an optimal linear routing. This is useful for certain circuits that are composed of
+    this circuit connectivity such as Berstein Vazirani and QFT. For example:
+
+      .. plot:
+
+         from qiskit.circuit import QuantumCircuit
+
+         qc = QuantumCircuit(10)
+         qc.h(0)
+         qc.cx(0, range(1, 5))
+         qc.h(9)
+         qc.cx(9, range(8, 4, -1))
+         qc.measure_all()
+         qc.draw("mpl")
+
+      .. plot:
+         :include-source:
+
+         from qiskit.circuit import QuantumCircuit
+         from qiskit.transpiler.passes import StarPreRouting
+
+         qc = QuantumCircuit(10)
+         qc.h(0)
+         qc.cx(0, range(1, 5))
+         qc.h(9)
+         qc.cx(9, range(8, 4, -1))
+         qc.measure_all()
+         StarPreRouting()(qc).draw("mpl")
diff --git a/test/python/transpiler/test_star_prerouting.py b/test/python/transpiler/test_star_prerouting.py
new file mode 100644
index 000000000000..ddc8096eefd7
--- /dev/null
+++ b/test/python/transpiler/test_star_prerouting.py
@@ -0,0 +1,484 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2023.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+# pylint: disable=missing-function-docstring
+
+"""Test the StarPreRouting pass"""
+
+import unittest
+from test import QiskitTestCase
+import ddt
+
+from qiskit.circuit.library import QFT
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.converters import (
+    circuit_to_dag,
+    dag_to_circuit,
+)
+from qiskit.quantum_info import Operator
+from qiskit.transpiler.passes import VF2Layout, ApplyLayout, SabreSwap, SabreLayout
+from qiskit.transpiler.passes.routing.star_prerouting import StarPreRouting
+from qiskit.transpiler.coupling import CouplingMap
+from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
+from qiskit.utils.optionals import HAS_AER
+
+
+@ddt.ddt
+class TestStarPreRouting(QiskitTestCase):
+    """Tests the StarPreRouting pass"""
+
+    def test_simple_ghz_dagcircuit(self):
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.cx(0, range(1, 5))
+        dag = circuit_to_dag(qc)
+        new_dag = StarPreRouting().run(dag)
+        new_qc = dag_to_circuit(new_dag)
+
+        expected = QuantumCircuit(5)
+        expected.h(0)
+        expected.cx(0, 1)
+        expected.cx(0, 2)
+        expected.swap(0, 2)
+        expected.cx(2, 3)
+        expected.swap(2, 3)
+        expected.cx(3, 4)
+        # expected.swap(3,4)
+
+        self.assertTrue(Operator(expected).equiv(Operator(new_qc)))
+
+    def test_simple_ghz_dagdependency(self):
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.cx(0, range(1, 5))
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+
+        result = pm.run(qc)
+
+        self.assertTrue(Operator.from_circuit(result).equiv(Operator(qc)))
+
+    def test_double_ghz_dagcircuit(self):
+        qc = QuantumCircuit(10)
+        qc.h(0)
+        qc.cx(0, range(1, 5))
+        qc.h(9)
+        qc.cx(9, range(8, 4, -1))
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+        new_qc = pm.run(qc)
+
+        self.assertTrue(Operator.from_circuit(new_qc).equiv(Operator(qc)))
+
+    def test_double_ghz_dagdependency(self):
+        qc = QuantumCircuit(10)
+        qc.h(0)
+        qc.cx(0, range(1, 5))
+        qc.h(9)
+        qc.cx(9, range(8, 4, -1))
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+        new_qc = pm.run(qc)
+
+        self.assertTrue(Operator(qc).equiv(Operator.from_circuit(new_qc)))
+
+    def test_mixed_double_ghz_dagdependency(self):
+        """Shows off the power of using commutation analysis."""
+        qc = QuantumCircuit(4)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+
+        qc.cx(3, 1)
+        qc.cx(3, 2)
+
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+
+        qc.cx(3, 1)
+        qc.cx(3, 2)
+
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+
+        qc.cx(3, 1)
+        qc.cx(3, 2)
+
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+
+        qc.cx(3, 1)
+        qc.cx(3, 2)
+        # qc.measure_all()
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+
+        result = pm.run(qc)
+
+        self.assertTrue(Operator.from_circuit(result).equiv(Operator(qc)))
+
+    def test_double_ghz(self):
+        qc = QuantumCircuit(10)
+        qc.h(0)
+        qc.cx(0, range(1, 5))
+        qc.h(9)
+        qc.cx(9, range(8, 4, -1))
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+        result = pm.run(qc)
+
+        self.assertEqual(Operator.from_circuit(result), Operator(qc))
+
+    def test_linear_ghz_no_change(self):
+        qc = QuantumCircuit(6)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.cx(1, 2)
+        qc.cx(2, 3)
+        qc.cx(3, 4)
+        qc.cx(4, 5)
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+
+        result = pm.run(qc)
+
+        self.assertEqual(Operator.from_circuit(result), Operator(qc))
+
+    def test_no_star(self):
+        qc = QuantumCircuit(6)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.cx(3, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.cx(1, 4)
+        qc.cx(2, 1)
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+        result = pm.run(qc)
+
+        self.assertTrue(Operator.from_circuit(result).equiv(qc))
+
+    def test_10q_bv(self):
+        num_qubits = 10
+        qc = QuantumCircuit(num_qubits, num_qubits - 1)
+        qc.x(num_qubits - 1)
+        qc.h(qc.qubits)
+        for i in range(num_qubits - 1):
+            qc.cx(i, num_qubits - 1)
+        qc.barrier()
+        qc.h(qc.qubits[:-1])
+        for i in range(num_qubits - 1):
+            qc.measure(i, i)
+        result = StarPreRouting()(qc)
+
+        expected = QuantumCircuit(num_qubits, num_qubits - 1)
+        expected.h(0)
+        expected.h(1)
+        expected.h(2)
+        expected.h(3)
+        expected.h(4)
+        expected.h(5)
+        expected.h(6)
+        expected.h(7)
+        expected.h(8)
+        expected.x(9)
+        expected.h(9)
+        expected.cx(0, 9)
+        expected.cx(1, 9)
+        expected.swap(1, 9)
+        expected.cx(2, 1)
+        expected.swap(2, 1)
+        expected.cx(3, 2)
+        expected.swap(3, 2)
+        expected.cx(4, 3)
+        expected.swap(4, 3)
+        expected.cx(5, 4)
+        expected.swap(5, 4)
+        expected.cx(6, 5)
+        expected.swap(6, 5)
+        expected.cx(7, 6)
+        expected.swap(7, 6)
+        expected.cx(8, 7)
+        expected.barrier()
+        expected.h(0)
+        expected.h(1)
+        expected.h(2)
+        expected.h(3)
+        expected.h(4)
+        expected.h(5)
+        expected.h(6)
+        expected.h(8)
+        expected.h(9)
+        expected.measure(0, 0)
+        expected.measure(9, 1)
+        expected.measure(1, 2)
+        expected.measure(2, 3)
+        expected.measure(3, 4)
+        expected.measure(4, 5)
+        expected.measure(5, 6)
+        expected.measure(6, 7)
+        expected.measure(8, 8)
+        self.assertEqual(result, expected)
+
+    # Skip level 3 because of unitary synth introducing non-clifford gates
+    @unittest.skipUnless(HAS_AER, "Aer required for clifford simulation")
+    @ddt.data(0, 1)
+    def test_100q_grid_full_path(self, opt_level):
+        from qiskit_aer import AerSimulator
+
+        num_qubits = 100
+        coupling_map = CouplingMap.from_grid(10, 10)
+        qc = QuantumCircuit(num_qubits, num_qubits - 1)
+        qc.x(num_qubits - 1)
+        qc.h(qc.qubits)
+        for i in range(num_qubits - 1):
+            qc.cx(i, num_qubits - 1)
+        qc.barrier()
+        qc.h(qc.qubits[:-1])
+        for i in range(num_qubits - 1):
+            qc.measure(i, i)
+        pm = generate_preset_pass_manager(
+            opt_level, basis_gates=["h", "cx", "x"], coupling_map=coupling_map
+        )
+        pm.init += StarPreRouting()
+        result = pm.run(qc)
+        counts_before = AerSimulator().run(qc).result().get_counts()
+        counts_after = AerSimulator().run(result).result().get_counts()
+        self.assertEqual(counts_before, counts_after)
+
+    def test_10q_bv_no_barrier(self):
+        num_qubits = 6
+        qc = QuantumCircuit(num_qubits, num_qubits - 1)
+        qc.x(num_qubits - 1)
+        qc.h(qc.qubits)
+        for i in range(num_qubits - 1):
+            qc.cx(i, num_qubits - 1)
+        qc.h(qc.qubits[:-1])
+
+        pm = generate_preset_pass_manager(optimization_level=3, seed_transpiler=42)
+        pm.init += StarPreRouting()
+
+        result = pm.run(qc)
+        self.assertTrue(Operator.from_circuit(result).equiv(Operator(qc)))
+
+    # Skip level 3 because of unitary synth introducing non-clifford gates
+    @unittest.skipUnless(HAS_AER, "Aer required for clifford simulation")
+    @ddt.data(0, 1)
+    def test_100q_grid_full_path_no_barrier(self, opt_level):
+        from qiskit_aer import AerSimulator
+
+        num_qubits = 100
+        coupling_map = CouplingMap.from_grid(10, 10)
+        qc = QuantumCircuit(num_qubits, num_qubits - 1)
+        qc.x(num_qubits - 1)
+        qc.h(qc.qubits)
+        for i in range(num_qubits - 1):
+            qc.cx(i, num_qubits - 1)
+        qc.h(qc.qubits[:-1])
+        for i in range(num_qubits - 1):
+            qc.measure(i, i)
+        pm = generate_preset_pass_manager(
+            opt_level, basis_gates=["h", "cx", "x"], coupling_map=coupling_map
+        )
+        pm.init += StarPreRouting()
+        result = pm.run(qc)
+        counts_before = AerSimulator().run(qc).result().get_counts()
+        counts_after = AerSimulator().run(result).result().get_counts()
+        self.assertEqual(counts_before, counts_after)
+
+    def test_hadamard_ordering(self):
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.h(0)
+        qc.cx(0, 2)
+        qc.h(0)
+        qc.cx(0, 3)
+        qc.h(0)
+        qc.cx(0, 4)
+        result = StarPreRouting()(qc)
+        expected = QuantumCircuit(5)
+        expected.h(0)
+        expected.cx(0, 1)
+        expected.h(0)
+        expected.cx(0, 2)
+        expected.swap(0, 2)
+        expected.h(2)
+        expected.cx(2, 3)
+        expected.swap(2, 3)
+        expected.h(3)
+        expected.cx(3, 4)
+        # expected.swap(3, 4)
+        self.assertEqual(expected, result)
+
+    def test_count_1_stars_starting_center(self):
+        qc = QuantumCircuit(6)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.cx(0, 5)
+        spr = StarPreRouting()
+
+        star_blocks, _ = spr.determine_star_blocks_processing(circuit_to_dag(qc), min_block_size=2)
+        self.assertEqual(len(star_blocks), 1)
+        self.assertEqual(len(star_blocks[0].nodes), 5)
+
+    def test_count_1_stars_starting_branch(self):
+        qc = QuantumCircuit(6)
+        qc.cx(1, 0)
+        qc.cx(2, 0)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.cx(0, 5)
+        spr = StarPreRouting()
+        _ = spr(qc)
+
+        star_blocks, _ = spr.determine_star_blocks_processing(circuit_to_dag(qc), min_block_size=2)
+        self.assertEqual(len(star_blocks), 1)
+        self.assertEqual(len(star_blocks[0].nodes), 5)
+
+    def test_count_2_stars(self):
+        qc = QuantumCircuit(6)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.cx(0, 5)
+
+        qc.cx(1, 2)
+        qc.cx(1, 3)
+        qc.cx(1, 4)
+        qc.cx(1, 5)
+        spr = StarPreRouting()
+        _ = spr(qc)
+
+        star_blocks, _ = spr.determine_star_blocks_processing(circuit_to_dag(qc), min_block_size=2)
+        self.assertEqual(len(star_blocks), 2)
+        self.assertEqual(len(star_blocks[0].nodes), 5)
+        self.assertEqual(len(star_blocks[1].nodes), 4)
+
+    def test_count_3_stars(self):
+        qc = QuantumCircuit(6)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.cx(0, 5)
+
+        qc.cx(1, 2)
+        qc.cx(1, 3)
+        qc.cx(1, 4)
+        qc.cx(1, 5)
+
+        qc.cx(2, 3)
+        qc.cx(2, 4)
+        qc.cx(2, 5)
+        spr = StarPreRouting()
+        star_blocks, _ = spr.determine_star_blocks_processing(circuit_to_dag(qc), min_block_size=2)
+
+        self.assertEqual(len(star_blocks), 3)
+        self.assertEqual(len(star_blocks[0].nodes), 5)
+        self.assertEqual(len(star_blocks[1].nodes), 4)
+        self.assertEqual(len(star_blocks[2].nodes), 3)
+
+    def test_count_70_qft_stars(self):
+        qft_module = QFT(10, do_swaps=False).decompose()
+        qftqc = QuantumCircuit(100)
+        for i in range(10):
+            qftqc.compose(qft_module, qubits=range(i * 10, (i + 1) * 10), inplace=True)
+        spr = StarPreRouting()
+        star_blocks, _ = spr.determine_star_blocks_processing(
+            circuit_to_dag(qftqc), min_block_size=2
+        )
+
+        self.assertEqual(len(star_blocks), 80)
+        star_len_list = [len([n for n in b.nodes if len(n.qargs) > 1]) for b in star_blocks]
+        expected_star_size = {2, 3, 4, 5, 6, 7, 8, 9}
+        self.assertEqual(set(star_len_list), expected_star_size)
+        for i in expected_star_size:
+            self.assertEqual(star_len_list.count(i), 10)
+
+    def test_count_50_qft_stars(self):
+        qft_module = QFT(10, do_swaps=False).decompose()
+        qftqc = QuantumCircuit(10)
+        for _ in range(10):
+            qftqc.compose(qft_module, qubits=range(10), inplace=True)
+        spr = StarPreRouting()
+        _ = spr(qftqc)
+
+        star_blocks, _ = spr.determine_star_blocks_processing(
+            circuit_to_dag(qftqc), min_block_size=2
+        )
+        self.assertEqual(len(star_blocks), 50)
+        star_len_list = [len([n for n in b.nodes if len(n.qargs) > 1]) for b in star_blocks]
+        expected_star_size = {9}
+        self.assertEqual(set(star_len_list), expected_star_size)
+
+    def test_two_star_routing(self):
+        qc = QuantumCircuit(4)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+
+        qc.cx(2, 3)
+        qc.cx(2, 1)
+
+        spr = StarPreRouting()
+        res = spr(qc)
+
+        self.assertTrue(Operator.from_circuit(res).equiv(qc))
+
+    def test_detect_two_opposite_stars_barrier(self):
+        qc = QuantumCircuit(6)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.barrier()
+        qc.cx(5, 1)
+        qc.cx(5, 2)
+        qc.cx(5, 3)
+        qc.cx(5, 4)
+
+        spr = StarPreRouting()
+        star_blocks, _ = spr.determine_star_blocks_processing(circuit_to_dag(qc), min_block_size=2)
+        self.assertEqual(len(star_blocks), 2)
+        self.assertEqual(len(star_blocks[0].nodes), 4)
+        self.assertEqual(len(star_blocks[1].nodes), 4)
+
+    def test_routing_after_star_prerouting(self):
+        nq = 6
+        qc = QFT(nq, do_swaps=False, insert_barriers=True).decompose()
+        cm = CouplingMap.from_line(nq)
+
+        pm_preroute = PassManager()
+        pm_preroute.append(StarPreRouting())
+        pm_preroute.append(VF2Layout(coupling_map=cm, seed=17))
+        pm_preroute.append(ApplyLayout())
+        pm_preroute.append(SabreSwap(coupling_map=cm, seed=17))
+
+        pm_sabre = PassManager()
+        pm_sabre.append(SabreLayout(coupling_map=cm, seed=17))
+
+        res_sabre = pm_sabre.run(qc)
+        res_star = pm_sabre.run(qc)
+
+        self.assertTrue(Operator.from_circuit(res_sabre), qc)
+        self.assertTrue(Operator.from_circuit(res_star), qc)
+        self.assertTrue(Operator.from_circuit(res_star), Operator.from_circuit(res_sabre))

From bb1ef16abc1b9e8ff2d87c4c26ece0ff8e481b57 Mon Sep 17 00:00:00 2001
From: Alexander Ivrii 
Date: Thu, 2 May 2024 20:34:15 +0300
Subject: [PATCH 062/179] Conjugate reduction in optimize annotated pass
 (#11811)

* initial commit for the conjugate reduction optimization

* do not go into definitions when unnecessary

* release notes

* typo

* minor

* rewriting as list comprehension

* improving release notes

* removing print statement

* changing op_predecessors and op_successor methods to return iterators rather than lists

* and removing explcit iter

* constructing the optimized circuit using compose rather than append

* improving variable names

* adding test

* adding tests exploring which gates get collected

* more renaming

---------

Co-authored-by: Matthew Treinish 
---
 qiskit/dagcircuit/dagcircuit.py               |   8 +
 .../passes/optimization/optimize_annotated.py | 258 +++++++++++++++++-
 ...-conjugate-reduction-656438d3642f27dc.yaml |  24 ++
 .../transpiler/test_optimize_annotated.py     | 226 ++++++++++++++-
 4 files changed, 504 insertions(+), 12 deletions(-)
 create mode 100644 releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml

diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index 838e9cfe0f86..831851bee36d 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -1879,6 +1879,14 @@ def predecessors(self, node):
         """Returns iterator of the predecessors of a node as DAGOpNodes and DAGInNodes."""
         return iter(self._multi_graph.predecessors(node._node_id))
 
+    def op_successors(self, node):
+        """Returns iterator of "op" successors of a node in the dag."""
+        return (succ for succ in self.successors(node) if isinstance(succ, DAGOpNode))
+
+    def op_predecessors(self, node):
+        """Returns the iterator of "op" predecessors of a node in the dag."""
+        return (pred for pred in self.predecessors(node) if isinstance(pred, DAGOpNode))
+
     def is_successor(self, node, node_succ):
         """Checks if a second node is in the successors of node."""
         return self._multi_graph.has_edge(node._node_id, node_succ._node_id)
diff --git a/qiskit/transpiler/passes/optimization/optimize_annotated.py b/qiskit/transpiler/passes/optimization/optimize_annotated.py
index 0b9b786a07f4..fe6fe7f49e78 100644
--- a/qiskit/transpiler/passes/optimization/optimize_annotated.py
+++ b/qiskit/transpiler/passes/optimization/optimize_annotated.py
@@ -12,12 +12,19 @@
 
 """Optimize annotated operations on a circuit."""
 
-from typing import Optional, List, Tuple
+from typing import Optional, List, Tuple, Union
 
 from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
 from qiskit.converters import circuit_to_dag, dag_to_circuit
 from qiskit.circuit.annotated_operation import AnnotatedOperation, _canonicalize_modifiers
-from qiskit.circuit import EquivalenceLibrary, ControlledGate, Operation, ControlFlowOp
+from qiskit.circuit import (
+    QuantumCircuit,
+    Instruction,
+    EquivalenceLibrary,
+    ControlledGate,
+    Operation,
+    ControlFlowOp,
+)
 from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.transpiler.passes.utils import control_flow
 from qiskit.transpiler.target import Target
@@ -43,6 +50,11 @@ class OptimizeAnnotated(TransformationPass):
       ``g2 = AnnotatedOperation(g1, ControlModifier(2))``, then ``g2`` can be replaced with
       ``AnnotatedOperation(SwapGate(), [InverseModifier(), ControlModifier(2)])``.
 
+    * Applies conjugate reduction to annotated operations. As an example,
+      ``control - [P -- Q -- P^{-1}]`` can be rewritten as ``P -- control - [Q] -- P^{-1}``,
+      that is, only the middle part needs to be controlled. This also works for inverse
+      and power modifiers.
+
     """
 
     def __init__(
@@ -51,6 +63,7 @@ def __init__(
         equivalence_library: Optional[EquivalenceLibrary] = None,
         basis_gates: Optional[List[str]] = None,
         recurse: bool = True,
+        do_conjugate_reduction: bool = True,
     ):
         """
         OptimizeAnnotated initializer.
@@ -67,12 +80,14 @@ def __init__(
                 not applied when neither is specified since such objects do not need to
                 be synthesized). Setting this value to ``False`` precludes the recursion in
                 every case.
+            do_conjugate_reduction: controls whether conjugate reduction should be performed.
         """
         super().__init__()
 
         self._target = target
         self._equiv_lib = equivalence_library
         self._basis_gates = basis_gates
+        self._do_conjugate_reduction = do_conjugate_reduction
 
         self._top_level_only = not recurse or (self._basis_gates is None and self._target is None)
 
@@ -122,7 +137,11 @@ def _run_inner(self, dag) -> Tuple[DAGCircuit, bool]:
             # as they may remove annotated gates.
             dag, opt2 = self._recurse(dag)
 
-        return dag, opt1 or opt2
+        opt3 = False
+        if not self._top_level_only and self._do_conjugate_reduction:
+            dag, opt3 = self._conjugate_reduction(dag)
+
+        return dag, opt1 or opt2 or opt3
 
     def _canonicalize(self, dag) -> Tuple[DAGCircuit, bool]:
         """
@@ -148,17 +167,219 @@ def _canonicalize(self, dag) -> Tuple[DAGCircuit, bool]:
             did_something = True
         return dag, did_something
 
-    def _recursively_process_definitions(self, op: Operation) -> bool:
+    def _conjugate_decomposition(
+        self, dag: DAGCircuit
+    ) -> Union[Tuple[DAGCircuit, DAGCircuit, DAGCircuit], None]:
         """
-        Recursively applies optimizations to op's definition (or to op.base_op's
-        definition if op is an annotated operation).
-        Returns True if did something.
+        Decomposes a circuit ``A`` into 3 sub-circuits ``P``, ``Q``, ``R`` such that
+        ``A = P -- Q -- R`` and ``R = P^{-1}``.
+
+        This is accomplished by iteratively finding inverse nodes at the front and at the back of the
+        circuit.
         """
 
-        # If op is an annotated operation, we descend into its base_op
-        if isinstance(op, AnnotatedOperation):
-            return self._recursively_process_definitions(op.base_op)
+        front_block = []  # nodes collected from the front of the circuit (aka P)
+        back_block = []  # nodes collected from the back of the circuit (aka R)
+
+        # Stores in- and out- degree for each node. These degrees are computed at the start of this
+        # function and are updated when nodes are collected into front_block or into back_block.
+        in_degree = {}
+        out_degree = {}
+
+        # We use dicts to track for each qubit a DAG node at the front of the circuit that involves
+        # this qubit and a DAG node at the end of the circuit that involves this qubit (when exist).
+        # Note that for the DAGCircuit structure for each qubit there can be at most one such front
+        # and such back node.
+        # This allows for an efficient way to find an inverse pair of gates (one from the front and
+        # one from the back of the circuit).
+        # A qubit that was never examined does not appear in these dicts, and a qubit that was examined
+        # but currently is not involved at the front (resp. at the back) of the circuit has the value of
+        # None.
+        front_node_for_qubit = {}
+        back_node_for_qubit = {}
+
+        # Keep the set of nodes that have been moved either to front_block or to back_block
+        processed_nodes = set()
+
+        # Keep the set of qubits that are involved in nodes at the front or at the back of the circuit.
+        # When looking for inverse pairs of gates we will only iterate over these qubits.
+        active_qubits = set()
+
+        # Keep pairs of nodes for which the inverse check was performed and the nodes
+        # were found to be not inverse to each other (memoization).
+        checked_node_pairs = set()
+
+        # compute in- and out- degree for every node
+        # also update information for nodes at the start and at the end of the circuit
+        for node in dag.op_nodes():
+            preds = list(dag.op_predecessors(node))
+            in_degree[node] = len(preds)
+            if len(preds) == 0:
+                for q in node.qargs:
+                    front_node_for_qubit[q] = node
+                    active_qubits.add(q)
+            succs = list(dag.op_successors(node))
+            out_degree[node] = len(succs)
+            if len(succs) == 0:
+                for q in node.qargs:
+                    back_node_for_qubit[q] = node
+                    active_qubits.add(q)
+
+        # iterate while there is a possibility to find more inverse pairs
+        while len(active_qubits) > 0:
+            to_check = active_qubits.copy()
+            active_qubits.clear()
+
+            # For each qubit q, check whether the gate at the front of the circuit that involves q
+            # and the gate at the end of the circuit that involves q are inverse
+            for q in to_check:
+
+                if (front_node := front_node_for_qubit.get(q, None)) is None:
+                    continue
+                if (back_node := back_node_for_qubit.get(q, None)) is None:
+                    continue
+
+                # front_node or back_node could be already collected when considering other qubits
+                if front_node in processed_nodes or back_node in processed_nodes:
+                    continue
+
+                # it is possible that the same node is both at the front and at the back,
+                # it should not be collected
+                if front_node == back_node:
+                    continue
+
+                # have been checked before
+                if (front_node, back_node) in checked_node_pairs:
+                    continue
+
+                # fast check based on the arguments
+                if front_node.qargs != back_node.qargs or front_node.cargs != back_node.cargs:
+                    continue
+
+                # in the future we want to include a more precise check whether a pair
+                # of nodes are inverse
+                if front_node.op == back_node.op.inverse():
+                    # update front_node_for_qubit and back_node_for_qubit
+                    for q in front_node.qargs:
+                        front_node_for_qubit[q] = None
+                    for q in back_node.qargs:
+                        back_node_for_qubit[q] = None
+
+                    # see which other nodes become at the front and update information
+                    for node in dag.op_successors(front_node):
+                        if node not in processed_nodes:
+                            in_degree[node] -= 1
+                            if in_degree[node] == 0:
+                                for q in node.qargs:
+                                    front_node_for_qubit[q] = node
+                                    active_qubits.add(q)
+
+                    # see which other nodes become at the back and update information
+                    for node in dag.op_predecessors(back_node):
+                        if node not in processed_nodes:
+                            out_degree[node] -= 1
+                            if out_degree[node] == 0:
+                                for q in node.qargs:
+                                    back_node_for_qubit[q] = node
+                                    active_qubits.add(q)
+
+                    # collect and mark as processed
+                    front_block.append(front_node)
+                    back_block.append(back_node)
+                    processed_nodes.add(front_node)
+                    processed_nodes.add(back_node)
+
+                else:
+                    checked_node_pairs.add((front_node, back_node))
+
+        # if nothing is found, return None
+        if len(front_block) == 0:
+            return None
+
+        # create the output DAGs
+        front_circuit = dag.copy_empty_like()
+        middle_circuit = dag.copy_empty_like()
+        back_circuit = dag.copy_empty_like()
+        front_circuit.global_phase = 0
+        back_circuit.global_phase = 0
+
+        for node in front_block:
+            front_circuit.apply_operation_back(node.op, node.qargs, node.cargs)
+
+        for node in back_block:
+            back_circuit.apply_operation_front(node.op, node.qargs, node.cargs)
+
+        for node in dag.op_nodes():
+            if node not in processed_nodes:
+                middle_circuit.apply_operation_back(node.op, node.qargs, node.cargs)
+
+        return front_circuit, middle_circuit, back_circuit
+
+    def _conjugate_reduce_op(
+        self, op: AnnotatedOperation, base_decomposition: Tuple[DAGCircuit, DAGCircuit, DAGCircuit]
+    ) -> Operation:
+        """
+        We are given an annotated-operation ``op = M [ B ]`` (where ``B`` is the base operation and
+        ``M`` is the list of modifiers) and the "conjugate decomposition" of the definition of ``B``,
+        i.e. ``B = P * Q * R``, with ``R = P^{-1}`` (with ``P``, ``Q`` and ``R`` represented as
+        ``DAGCircuit`` objects).
+
+        Let ``IQ`` denote a new custom instruction with definitions ``Q``.
+
+        We return the operation ``op_new`` which a new custom instruction with definition
+        ``P * A * R``, where ``A`` is a new annotated-operation with modifiers ``M`` and
+        base gate ``IQ``.
+        """
+        p_dag, q_dag, r_dag = base_decomposition
+
+        q_instr = Instruction(
+            name="iq", num_qubits=op.base_op.num_qubits, num_clbits=op.base_op.num_clbits, params=[]
+        )
+        q_instr.definition = dag_to_circuit(q_dag)
+
+        op_new = Instruction(
+            "optimized", num_qubits=op.num_qubits, num_clbits=op.num_clbits, params=[]
+        )
+        num_control_qubits = op.num_qubits - op.base_op.num_qubits
+
+        circ = QuantumCircuit(op.num_qubits, op.num_clbits)
+        qubits = circ.qubits
+        circ.compose(
+            dag_to_circuit(p_dag), qubits[num_control_qubits : op.num_qubits], inplace=True
+        )
+        circ.append(
+            AnnotatedOperation(base_op=q_instr, modifiers=op.modifiers), range(op.num_qubits)
+        )
+        circ.compose(
+            dag_to_circuit(r_dag), qubits[num_control_qubits : op.num_qubits], inplace=True
+        )
+        op_new.definition = circ
+        return op_new
+
+    def _conjugate_reduction(self, dag) -> Tuple[DAGCircuit, bool]:
+        """
+        Looks for annotated operations whose base operation has a nontrivial conjugate decomposition.
+        In such cases, the modifiers of the annotated operation can be moved to the "middle" part of
+        the decomposition.
 
+        Returns the modified DAG and whether it did something.
+        """
+        did_something = False
+        for node in dag.op_nodes(op=AnnotatedOperation):
+            base_op = node.op.base_op
+            if not self._skip_definition(base_op):
+                base_dag = circuit_to_dag(base_op.definition, copy_operations=False)
+                base_decomposition = self._conjugate_decomposition(base_dag)
+                if base_decomposition is not None:
+                    new_op = self._conjugate_reduce_op(node.op, base_decomposition)
+                    dag.substitute_node(node, new_op)
+                    did_something = True
+        return dag, did_something
+
+    def _skip_definition(self, op: Operation) -> bool:
+        """
+        Returns True if we should not recurse into a gate's definition.
+        """
         # Similar to HighLevelSynthesis transpiler pass, we do not recurse into a gate's
         # `definition` for a gate that is supported by the target or in equivalence library.
 
@@ -170,7 +391,22 @@ def _recursively_process_definitions(self, op: Operation) -> bool:
                 else op.name in self._device_insts
             )
             if inst_supported or (self._equiv_lib is not None and self._equiv_lib.has_entry(op)):
-                return False
+                return True
+        return False
+
+    def _recursively_process_definitions(self, op: Operation) -> bool:
+        """
+        Recursively applies optimizations to op's definition (or to op.base_op's
+        definition if op is an annotated operation).
+        Returns True if did something.
+        """
+
+        # If op is an annotated operation, we descend into its base_op
+        if isinstance(op, AnnotatedOperation):
+            return self._recursively_process_definitions(op.base_op)
+
+        if self._skip_definition(op):
+            return False
 
         try:
             # extract definition
diff --git a/releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml b/releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml
new file mode 100644
index 000000000000..231f0e7c8f3e
--- /dev/null
+++ b/releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml
@@ -0,0 +1,24 @@
+features:
+  - |
+    Added a new reduction to the :class:`.OptimizeAnnotated` transpiler pass.
+    This reduction looks for annotated operations (objects of type :class:`.AnnotatedOperation`
+    that consist of a base operation ``B`` and a list ``M`` of control, inverse and power
+    modifiers) with the following properties:
+
+    * the base operation ``B`` needs to be synthesized (i.e. it's not already supported
+      by the target or belongs to the equivalence library)
+
+    * the definition circuit for ``B`` can be expressed as ``P -- Q -- R`` with :math:`R = P^{-1}`
+
+    In this case the modifiers can be moved to the ``Q``-part only. As a specific example,
+    controlled QFT-based adders have the form ``control - [QFT -- U -- IQFT]``, which can be
+    simplified to ``QFT -- control-[U] -- IQFT``. By removing the controls over ``QFT`` and
+    ``IQFT`` parts of the circuit, one obtains significantly fewer gates in the transpiled
+    circuit.
+  - |
+    Added two new methods to the :class:`~qiskit.dagcircuit.DAGCircuit` class:
+    :meth:`qiskit.dagcircuit.DAGCircuit.op_successors` returns an iterator to
+    :class:`.DAGOpNode` successors of a node, and
+    :meth:`qiskit.dagcircuit.DAGCircuit.op_successors` returns an iterator to
+    :class:`.DAGOpNode` predecessors of a node.
+
diff --git a/test/python/transpiler/test_optimize_annotated.py b/test/python/transpiler/test_optimize_annotated.py
index 5e573b551dd5..0b15b79e2cf1 100644
--- a/test/python/transpiler/test_optimize_annotated.py
+++ b/test/python/transpiler/test_optimize_annotated.py
@@ -22,6 +22,7 @@
     PowerModifier,
 )
 from qiskit.transpiler.passes import OptimizeAnnotated
+from qiskit.quantum_info import Operator
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -102,7 +103,9 @@ def test_optimize_definitions(self):
         qc.h(0)
         qc.append(gate, [0, 1, 3])
 
-        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+        # Add "swap" to the basis gates to prevent conjugate reduction from replacing
+        # control-[SWAP] by CX(0,1) -- CCX(1, 0) -- CX(0, 1)
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u", "swap"])(qc)
         self.assertEqual(qc_optimized[1].operation.definition, expected_qc_def_optimized)
 
     def test_do_not_optimize_definitions_without_basis_gates(self):
@@ -195,6 +198,227 @@ def test_if_else(self):
 
         self.assertEqual(qc_optimized, expected_qc)
 
+    def test_conjugate_reduction(self):
+        """Test conjugate reduction optimization."""
+
+        # Create a control-annotated operation.
+        # The definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.z(0)  # P
+        qc_def.s(0)  # P
+        qc_def.cx(0, 4)  # P
+        qc_def.cx(4, 3)  # P
+        qc_def.y(3)  # Q
+        qc_def.cx(3, 0)  # Q
+        qc_def.cx(4, 3)  # R
+        qc_def.cx(0, 4)  # R
+        qc_def.sdg(0)  # R
+        qc_def.z(0)  # R
+        qc_def.cx(0, 1)  # R
+        qc_def.z(5)  # P
+        qc_def.z(5)  # R
+        qc_def.x(2)  # Q
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation
+        qc = QuantumCircuit(8)
+        qc.cx(0, 2)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+        qc.h(0)
+        qc.z(4)
+
+        qc_keys = qc.count_ops().keys()
+        self.assertIn("annotated", qc_keys)
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # The pass should simplify the gate
+        qc_optimized_keys = qc_optimized.count_ops().keys()
+        self.assertIn("optimized", qc_optimized_keys)
+        self.assertNotIn("annotated", qc_optimized_keys)
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+    def test_conjugate_reduction_collection(self):
+        """Test conjugate reduction optimization including an assertion on which gates
+        are collected (using annotated gate from the previous example).
+        """
+
+        # Create a control-annotated operation.
+        # The definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.z(0)  # P
+        qc_def.s(0)  # P
+        qc_def.cx(0, 4)  # P
+        qc_def.cx(4, 3)  # P
+        qc_def.y(3)  # Q
+        qc_def.cx(3, 0)  # Q
+        qc_def.cx(4, 3)  # R
+        qc_def.cx(0, 4)  # R
+        qc_def.sdg(0)  # R
+        qc_def.z(0)  # R
+        qc_def.cx(0, 1)  # R
+        qc_def.z(5)  # P
+        qc_def.z(5)  # R
+        qc_def.x(2)  # Q
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation
+        qc = QuantumCircuit(8)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Check that the optimization finds correct pairs of inverse gates
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "s": 1, "sdg": 1, "z": 4, "cx": 6})
+
+    def test_conjugate_reduction_consecutive_gates(self):
+        """Test conjugate reduction optimization including an assertion on which gates
+        are collected (multiple consecutive gates on the same pair of qubits).
+        """
+
+        # Create a control-annotated operation.
+        # the definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.swap(0, 1)  # P
+        qc_def.cz(1, 2)  # Q
+        qc_def.swap(0, 1)  # R
+        qc_def.cx(0, 1)  # R
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation.
+        qc = QuantumCircuit(8)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Check that the optimization finds correct pairs of inverse gates
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "cx": 2, "swap": 2})
+
+    def test_conjugate_reduction_chain_of_gates(self):
+        """Test conjugate reduction optimization including an assertion on which gates
+        are collected (chain of gates).
+        """
+
+        # Create a control-annotated operation.
+        # the definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.cx(1, 2)  # P
+        qc_def.cx(2, 3)  # P
+        qc_def.h(3)  # Q
+        qc_def.cx(2, 3)  # R
+        qc_def.cx(1, 2)  # R
+        qc_def.cx(0, 1)  # R
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation.
+        qc = QuantumCircuit(8)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Check that the optimization finds correct pairs of inverse gates
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "cx": 6})
+
+    def test_conjugate_reduction_empty_middle(self):
+        """Test conjugate reduction optimization including an assertion on which gates
+        are collected (with no gates in the middle circuit).
+        """
+
+        # Create a control-annotated operation.
+        # the definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.swap(0, 1)  # P
+        qc_def.cz(1, 2)  # P
+        qc_def.cz(1, 2)  # R
+        qc_def.swap(0, 1)  # R
+        qc_def.cx(0, 1)  # R
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation.
+        qc = QuantumCircuit(8)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Check that the optimization finds correct pairs of inverse gates
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "cx": 2, "cz": 2, "swap": 2})
+
+    def test_conjugate_reduction_parallel_gates(self):
+        """Test conjugate reduction optimization including an assertion on which gates
+        are collected (multiple gates in front and back layers).
+        """
+
+        # Create a control-annotated operation.
+        # the definition of the base operation has conjugate decomposition P -- Q -- R with R = P^{-1}
+        qc_def = QuantumCircuit(6)
+        qc_def.cx(0, 1)  # P
+        qc_def.swap(2, 3)  # P
+        qc_def.cz(4, 5)  # P
+        qc_def.h(0)  # Q
+        qc_def.h(1)  # Q
+        qc_def.cx(0, 1)  # R
+        qc_def.swap(2, 3)  # R
+        qc_def.cz(4, 5)  # R
+        custom = qc_def.to_gate().control(annotated=True)
+
+        # Create a quantum circuit with an annotated operation.
+        qc = QuantumCircuit(8)
+        qc.append(custom, [0, 1, 3, 4, 5, 7, 6])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Check that the optimization finds correct pairs of inverse gates
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "cx": 2, "cz": 2, "swap": 2})
+
+    def test_conjugate_reduction_cswap(self):
+        """Test conjugate reduction optimization for control-SWAP."""
+
+        # Create a circuit with a control-annotated swap
+        qc = QuantumCircuit(3)
+        qc.append(SwapGate().control(annotated=True), [0, 1, 2])
+
+        # Run optimization pass
+        qc_optimized = OptimizeAnnotated(basis_gates=["cx", "u"])(qc)
+
+        # Check that the optimization is correct
+        self.assertEqual(Operator(qc), Operator(qc_optimized))
+
+        # Swap(0, 1) gets translated to CX(0, 1), CX(1, 0), CX(0, 1).
+        # The first and the last of the CXs should be detected as inverse of each other.
+        new_def_ops = dict(qc_optimized[0].operation.definition.count_ops())
+        self.assertEqual(new_def_ops, {"annotated": 1, "cx": 2})
+
     def test_standalone_var(self):
         """Test that standalone vars work."""
         a = expr.Var.new("a", types.Bool())

From 43c065fc1b2b440d6cf8c393b01dd04624733591 Mon Sep 17 00:00:00 2001
From: Alexander Ivrii 
Date: Thu, 2 May 2024 20:51:50 +0300
Subject: [PATCH 063/179] Add ElidePermutations pass to optimization level 3
 (#12111)

* Add ElideSwaps transpiler pass

This commit adds a new transpiler pass ElideSwaps which is a logical
optimization pass designed to run prior to layout or any other physical
embedding steps in the transpiler pipeline. It traverses the DAG in
topological order and when a swap gate is encountered it removes that
gate and instead permutes the logical qubits for any subsequent gates
in the DAG. This will eliminate any swaps in a circuit not caused by
routing. Additionally, this pass is added to the preset pass manager for
optimization level 3, we can consider adding it to other levels too if
we think it makes sense (there is little overhead, and almost 0 if there
are no swaps).

One thing to note is that this pass does not recurse into control flow
blocks at all, it treats them as black box operations. So if any control
flow blocks contain swap gates those will not be optimized away. This
change was made because mapping the permutation outside and inside any
control flow block was larger in scope than what the intent for this
pass was. That type of work is better suited for the routing passes
which already have to reason about this.

* Update tests with optimization level 3

* Pass final layout from ElideSwap to output

The new ElideSwap pass is causing an output permutation just as a
routing pass would. This information needs to be passed through to the
output in case there is no layout or routing run. In those cases the
information about the output permutation caused by the swap elision will
be lost and doing layout aware operations like Operator.from_circuit()
will not be able to reason about the permutation. This commit fixes this
by inserting the original layout and qubit mapping into the property set
along with the final layout. Then the base pass class and pass manager
class are updated to use the original layout as the initial layout if
one isn't set. In cases where we run layout and routing the output
metadata from those passes will superscede these new metadata fields.

* Move pass in opt level 3 earlier in stage and skip with explicit layout

This commit fixes 2 issues in the execution of the new ElideSwaps pass
as part of optimization level 3. First we were running it too late in
the process both after high level synthesis (which isn't relavant yet,
but will be soon when this is expanded to elide all permutations not
just swaps) and also after reset diagonal before measurement. The second
issue is that if the user is specifying to run with a manual layout set
we should not run this pass, as it will interfere with the user intent.

* Doc and copy paste fixes

* Expand test coverage

* Update permutation tracking

There were 2 issues with the permutation tracking done in an earlier
commit. First, it conflicted with the final_layout property set via
routing (or internally by the routing done in the combined sabre
layout). This was breaking conditional checks in the preset pass manager
around embedding. To fix this a different property is used and set as
the output final layout if no final layout is set. The second issue was
the output layout object was not taking into account a set initial
layout which will permute the qubits and cause the output to not be up
to date. This is fixed by updating apply layout to apply the initial
layout to the elision_final_layout in the property set.

* Generalize pass to support PermutationGate too

This commit generalizes the pass from just considering swap gates to all
permutations (via the PermutationGate class). This enables the pass to
elide additional circuit permutations, not just the special case of a
swap gate. The pass and module are renamed accordingly to
ElidePermutations and elide_permutations respectively.

* Fix permutation handling

This commit fixes the recently added handling of the PermutationGate so
that it correctly is mapping the qubits. The previous iteration of this
messed up the mapping logic so it wasn't valid.

* Fix formatting

* Fix final layout handling for no initial layout

* Improve documentation and log a warning if run post layout

* Fix final layout handling with no ElideSwaps being run

* Fix docs build

* Fix release note

* Fix typo

* Add test for routing and elide permutations

* Apply suggestions from code review

Co-authored-by: Jim Garrison 

* Rename test file to test_elide_permutations.py

* Apply suggestions from code review

Co-authored-by: Kevin Hartman 

* Fix test import after rebase

* fixing failing test cases

this should pass CI after merging #12057

* addresses kehas comments - thx

* Adding FinalyzeLayouts pass to pull the virtual circuit permutation from ElidePermutations to the final layout

* formatting

* partial rebase on top of 12057 + tests

* also need test_operator for partial rebase

* removing from transpiler flow for now; reworking elide tests

* also adding permutation gate to the example

* also temporarily reverting test_transpiler.py

* update to release notes

* minor fixes

* adding ElidePermutations and FinalizeLayouts to level 3

* fixing tests

* release notes

* properly merging with main (now that ElidePermutations is merged)

* and also deleting finalize_layouts

* Update releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml

* updating code comment

* Fixing failing test: now that ElidePermutations pass runs with optimization level 3, it does remove the SWAP gate. So we need to consider optimization_level=1 to assert that the extra pass does not remove SWAP gates

---------

Co-authored-by: Matthew Treinish 
Co-authored-by: Jim Garrison 
Co-authored-by: Kevin Hartman 
Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
---
 .../preset_passmanagers/builtin_plugins.py       |  4 ++--
 ...ermutations-to-pipeline-077dad03bd55ab9c.yaml |  9 +++++++++
 test/python/compiler/test_transpiler.py          | 16 ++++++++++++----
 .../python/transpiler/test_elide_permutations.py |  3 ++-
 4 files changed, 25 insertions(+), 7 deletions(-)
 create mode 100644 releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml

diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
index 8eddb9adf63e..f85b4d113c17 100644
--- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
+++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
@@ -26,7 +26,7 @@
 from qiskit.transpiler.passes import TrivialLayout
 from qiskit.transpiler.passes import CheckMap
 from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
-from qiskit.transpiler.passes import OptimizeSwapBeforeMeasure
+from qiskit.transpiler.passes import ElidePermutations
 from qiskit.transpiler.passes import RemoveDiagonalGatesBeforeMeasure
 from qiskit.transpiler.preset_passmanagers import common
 from qiskit.transpiler.preset_passmanagers.plugin import (
@@ -133,7 +133,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
                 pass_manager_config.unitary_synthesis_plugin_config,
                 pass_manager_config.hls_config,
             )
-            init.append(OptimizeSwapBeforeMeasure())
+            init.append(ElidePermutations())
             init.append(RemoveDiagonalGatesBeforeMeasure())
             init.append(
                 InverseCancellation(
diff --git a/releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml b/releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml
new file mode 100644
index 000000000000..ddc35ddcb987
--- /dev/null
+++ b/releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    The transpiler pass :class:`~.ElidePermutations`
+    runs by default with optimization level 2 and 3. Intuitively, removing
+    :class:`~.SwapGate`\s and :class:`~qiskit.circuit.library.PermutationGate`\s
+    in a virtual circuit is almost always beneficial, as it makes the circuit shorter
+    and easier to route. As :class:`~.OptimizeSwapBeforeMeasure` is a special case
+    of :class:`~.ElidePermutations`, it has been removed from optimization level 3.
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
index 2a8c86b27ab0..6166528f8868 100644
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -1750,8 +1750,11 @@ def test_translate_ecr_basis(self, optimization_level):
             optimization_level=optimization_level,
             seed_transpiler=42,
         )
-        self.assertEqual(res.count_ops()["ecr"], 9)
-        self.assertTrue(Operator(res).equiv(circuit))
+
+        # Swap gates get optimized away in opt. level 2, 3
+        expected_num_ecr_gates = 6 if optimization_level in (2, 3) else 9
+        self.assertEqual(res.count_ops()["ecr"], expected_num_ecr_gates)
+        self.assertEqual(Operator(circuit), Operator.from_circuit(res))
 
     def test_optimize_ecr_basis(self):
         """Test highest optimization level can optimize over ECR."""
@@ -1760,8 +1763,13 @@ def test_optimize_ecr_basis(self):
         circuit.iswap(0, 1)
 
         res = transpile(circuit, basis_gates=["u", "ecr"], optimization_level=3, seed_transpiler=42)
-        self.assertEqual(res.count_ops()["ecr"], 1)
-        self.assertTrue(Operator(res).equiv(circuit))
+
+        # an iswap gate is equivalent to (swap, CZ) up to single-qubit rotations. Normally, the swap gate
+        # in the circuit would cancel with the swap gate of the (swap, CZ), leaving a single CZ gate that
+        # can be realized via one ECR gate. However, with the introduction of ElideSwap, the swap gate
+        # cancellation can not occur anymore, thus requiring two ECR gates for the iswap gate.
+        self.assertEqual(res.count_ops()["ecr"], 2)
+        self.assertEqual(Operator(circuit), Operator.from_circuit(res))
 
     def test_approximation_degree_invalid(self):
         """Test invalid approximation degree raises."""
diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py
index d3d807834fc0..6e0379f4bc5b 100644
--- a/test/python/transpiler/test_elide_permutations.py
+++ b/test/python/transpiler/test_elide_permutations.py
@@ -304,6 +304,7 @@ class TestElidePermutationsInTranspileFlow(QiskitTestCase):
 
     def test_not_run_after_layout(self):
         """Test ElidePermutations doesn't do anything after layout."""
+
         qc = QuantumCircuit(3)
         qc.h(0)
         qc.swap(0, 2)
@@ -312,7 +313,7 @@ def test_not_run_after_layout(self):
         qc.h(1)
 
         spm = generate_preset_pass_manager(
-            optimization_level=3, initial_layout=list(range(2, -1, -1)), seed_transpiler=42
+            optimization_level=1, initial_layout=list(range(2, -1, -1)), seed_transpiler=42
         )
         spm.layout += ElidePermutations()
         res = spm.run(qc)

From 849fa0066d6a7bd299654c163e8ebedd1914f746 Mon Sep 17 00:00:00 2001
From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Date: Fri, 3 May 2024 00:08:49 +0400
Subject: [PATCH 064/179] Add SamplerPubResult (#12143)

* add SamplerPubResult

* add join_data

* Update qiskit/primitives/containers/sampler_pub_result.py

Co-authored-by: Ian Hincks 

* adding tests

* add join_data tests

* add reno

* fix linting

---------

Co-authored-by: Ian Hincks 
Co-authored-by: Ian Hincks 
---
 qiskit/primitives/backend_sampler_v2.py       |  12 +-
 qiskit/primitives/base/base_sampler.py        |   4 +-
 qiskit/primitives/containers/__init__.py      |   9 +-
 qiskit/primitives/containers/pub_result.py    |   2 +-
 .../containers/sampler_pub_result.py          |  74 +++++++++++++
 qiskit/primitives/statevector_sampler.py      |  12 +-
 .../sampler-pub-result-e64e7de1bae2d35e.yaml  |  17 +++
 .../containers/test_sampler_pub_result.py     | 104 ++++++++++++++++++
 8 files changed, 215 insertions(+), 19 deletions(-)
 create mode 100644 qiskit/primitives/containers/sampler_pub_result.py
 create mode 100644 releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml
 create mode 100644 test/python/primitives/containers/test_sampler_pub_result.py

diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py
index 3b560645af6d..ff7a32580fe3 100644
--- a/qiskit/primitives/backend_sampler_v2.py
+++ b/qiskit/primitives/backend_sampler_v2.py
@@ -29,8 +29,8 @@
     BitArray,
     DataBin,
     PrimitiveResult,
-    PubResult,
     SamplerPubLike,
+    SamplerPubResult,
 )
 from qiskit.primitives.containers.bit_array import _min_num_bytes
 from qiskit.primitives.containers.sampler_pub import SamplerPub
@@ -124,7 +124,7 @@ def options(self) -> Options:
 
     def run(
         self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
-    ) -> PrimitiveJob[PrimitiveResult[PubResult]]:
+    ) -> PrimitiveJob[PrimitiveResult[SamplerPubResult]]:
         if shots is None:
             shots = self._options.default_shots
         coerced_pubs = [SamplerPub.coerce(pub, shots) for pub in pubs]
@@ -142,7 +142,7 @@ def _validate_pubs(self, pubs: list[SamplerPub]):
                     UserWarning,
                 )
 
-    def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[PubResult]:
+    def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[SamplerPubResult]:
         pub_dict = defaultdict(list)
         # consolidate pubs with the same number of shots
         for i, pub in enumerate(pubs):
@@ -157,7 +157,7 @@ def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[PubResult]:
                 results[i] = pub_result
         return PrimitiveResult(results)
 
-    def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[PubResult]:
+    def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult]:
         """Compute results for pubs that all require the same value of ``shots``."""
         # prepare circuits
         bound_circuits = [pub.parameter_values.bind_all(pub.circuit) for pub in pubs]
@@ -197,7 +197,7 @@ def _postprocess_pub(
         shape: tuple[int, ...],
         meas_info: list[_MeasureInfo],
         max_num_bytes: int,
-    ) -> PubResult:
+    ) -> SamplerPubResult:
         """Converts the memory data into an array of bit arrays with the shape of the pub."""
         arrays = {
             item.creg_name: np.zeros(shape + (shots, item.num_bytes), dtype=np.uint8)
@@ -213,7 +213,7 @@ def _postprocess_pub(
         meas = {
             item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
         }
-        return PubResult(DataBin(**meas, shape=shape), metadata={})
+        return SamplerPubResult(DataBin(**meas, shape=shape), metadata={})
 
 
 def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]:
diff --git a/qiskit/primitives/base/base_sampler.py b/qiskit/primitives/base/base_sampler.py
index 1d071a157282..94c9a7681d06 100644
--- a/qiskit/primitives/base/base_sampler.py
+++ b/qiskit/primitives/base/base_sampler.py
@@ -23,8 +23,8 @@
 from qiskit.providers import JobV1 as Job
 
 from ..containers.primitive_result import PrimitiveResult
-from ..containers.pub_result import PubResult
 from ..containers.sampler_pub import SamplerPubLike
+from ..containers.sampler_pub_result import SamplerPubResult
 from . import validation
 from .base_primitive import BasePrimitive
 from .base_primitive_job import BasePrimitiveJob
@@ -165,7 +165,7 @@ class BaseSamplerV2(ABC):
     @abstractmethod
     def run(
         self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
-    ) -> BasePrimitiveJob[PrimitiveResult[PubResult]]:
+    ) -> BasePrimitiveJob[PrimitiveResult[SamplerPubResult]]:
         """Run and collect samples from each pub.
 
         Args:
diff --git a/qiskit/primitives/containers/__init__.py b/qiskit/primitives/containers/__init__.py
index 63c9c600de1d..62fb49a3fb91 100644
--- a/qiskit/primitives/containers/__init__.py
+++ b/qiskit/primitives/containers/__init__.py
@@ -15,11 +15,12 @@
 """
 
 
+from .bindings_array import BindingsArrayLike
 from .bit_array import BitArray
-from .data_bin import make_data_bin, DataBin
+from .data_bin import DataBin, make_data_bin
+from .estimator_pub import EstimatorPubLike
+from .observables_array import ObservableLike, ObservablesArrayLike
 from .primitive_result import PrimitiveResult
 from .pub_result import PubResult
-from .estimator_pub import EstimatorPubLike
 from .sampler_pub import SamplerPubLike
-from .bindings_array import BindingsArrayLike
-from .observables_array import ObservableLike, ObservablesArrayLike
+from .sampler_pub_result import SamplerPubResult
diff --git a/qiskit/primitives/containers/pub_result.py b/qiskit/primitives/containers/pub_result.py
index 369179e4629f..1facb850ade4 100644
--- a/qiskit/primitives/containers/pub_result.py
+++ b/qiskit/primitives/containers/pub_result.py
@@ -11,7 +11,7 @@
 # that they have been altered from the originals.
 
 """
-Base Pub class
+Base Pub result class
 """
 
 from __future__ import annotations
diff --git a/qiskit/primitives/containers/sampler_pub_result.py b/qiskit/primitives/containers/sampler_pub_result.py
new file mode 100644
index 000000000000..7a2d2a9e3fca
--- /dev/null
+++ b/qiskit/primitives/containers/sampler_pub_result.py
@@ -0,0 +1,74 @@
+# 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.
+
+"""
+Sampler Pub result class
+"""
+
+from __future__ import annotations
+
+from typing import Iterable
+
+import numpy as np
+
+from .bit_array import BitArray
+from .pub_result import PubResult
+
+
+class SamplerPubResult(PubResult):
+    """Result of Sampler Pub."""
+
+    def join_data(self, names: Iterable[str] | None = None) -> BitArray | np.ndarray:
+        """Join data from many registers into one data container.
+
+        Data is joined along the bits axis. For example, for :class:`~.BitArray` data, this corresponds
+        to bitstring concatenation.
+
+        Args:
+            names: Which registers to join. Their order is maintained, for example, given
+                ``["alpha", "beta"]``, the data from register ``alpha`` is placed to the left of the
+                data from register ``beta``. When ``None`` is given, this value is set to the
+                ordered list of register names, which will have been preserved from the input circuit
+                order.
+
+        Returns:
+            Joint data.
+
+        Raises:
+            ValueError: If specified names are empty.
+            ValueError: If specified name does not exist.
+            TypeError: If specified data comes from incompatible types.
+        """
+        if names is None:
+            names = list(self.data)
+            if not names:
+                raise ValueError("No entry exists in the data bin.")
+        else:
+            names = list(names)
+            if not names:
+                raise ValueError("An empty name list is given.")
+            for name in names:
+                if name not in self.data:
+                    raise ValueError(f"Name '{name}' does not exist.")
+
+        data = [self.data[name] for name in names]
+        if isinstance(data[0], BitArray):
+            if not all(isinstance(datum, BitArray) for datum in data):
+                raise TypeError("Data comes from incompatible types.")
+            joint_data = BitArray.concatenate_bits(data)
+        elif isinstance(data[0], np.ndarray):
+            if not all(isinstance(datum, np.ndarray) for datum in data):
+                raise TypeError("Data comes from incompatible types.")
+            joint_data = np.concatenate(data, axis=-1)
+        else:
+            raise TypeError("Data comes from incompatible types.")
+        return joint_data
diff --git a/qiskit/primitives/statevector_sampler.py b/qiskit/primitives/statevector_sampler.py
index c78865aec7e6..90fe452ad124 100644
--- a/qiskit/primitives/statevector_sampler.py
+++ b/qiskit/primitives/statevector_sampler.py
@@ -15,9 +15,9 @@
 
 from __future__ import annotations
 
+import warnings
 from dataclasses import dataclass
 from typing import Iterable
-import warnings
 
 import numpy as np
 from numpy.typing import NDArray
@@ -32,7 +32,7 @@
     BitArray,
     DataBin,
     PrimitiveResult,
-    PubResult,
+    SamplerPubResult,
     SamplerPubLike,
 )
 from .containers.sampler_pub import SamplerPub
@@ -154,7 +154,7 @@ def seed(self) -> np.random.Generator | int | None:
 
     def run(
         self, pubs: Iterable[SamplerPubLike], *, shots: int | None = None
-    ) -> PrimitiveJob[PrimitiveResult[PubResult]]:
+    ) -> PrimitiveJob[PrimitiveResult[SamplerPubResult]]:
         if shots is None:
             shots = self._default_shots
         coerced_pubs = [SamplerPub.coerce(pub, shots) for pub in pubs]
@@ -169,11 +169,11 @@ def run(
         job._submit()
         return job
 
-    def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[PubResult]:
+    def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[SamplerPubResult]:
         results = [self._run_pub(pub) for pub in pubs]
         return PrimitiveResult(results)
 
-    def _run_pub(self, pub: SamplerPub) -> PubResult:
+    def _run_pub(self, pub: SamplerPub) -> SamplerPubResult:
         circuit, qargs, meas_info = _preprocess_circuit(pub.circuit)
         bound_circuits = pub.parameter_values.bind_all(circuit)
         arrays = {
@@ -197,7 +197,7 @@ def _run_pub(self, pub: SamplerPub) -> PubResult:
         meas = {
             item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
         }
-        return PubResult(DataBin(**meas, shape=pub.shape), metadata={"shots": pub.shots})
+        return SamplerPubResult(DataBin(**meas, shape=pub.shape), metadata={"shots": pub.shots})
 
 
 def _preprocess_circuit(circuit: QuantumCircuit):
diff --git a/releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml b/releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml
new file mode 100644
index 000000000000..2c5c2a6e10c3
--- /dev/null
+++ b/releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml
@@ -0,0 +1,17 @@
+---
+features_primitives:
+  - |
+    The subclass :class:`~.SamplerPubResult` of :class:`~.PubResult` was added,
+    which :class:`~.BaseSamplerV2` implementations can return. The main feature
+    added in this new subclass is :meth:`~.SamplerPubResult.join_data`, which 
+    joins together (a subset of) the contents of :attr:`~.PubResult.data` into 
+    a single object. This enables the following patterns:
+
+    .. code:: python
+
+        job_result =  sampler.run([pub1, pub2, pub3], shots=123).result()
+
+        # assuming all returned data entries are BitArrays
+        counts1 = job_result[0].join_data().get_counts()
+        bistrings2 = job_result[1].join_data().get_bitstrings()
+        array3 = job_result[2].join_data().array
\ No newline at end of file
diff --git a/test/python/primitives/containers/test_sampler_pub_result.py b/test/python/primitives/containers/test_sampler_pub_result.py
new file mode 100644
index 000000000000..fe7144a0741b
--- /dev/null
+++ b/test/python/primitives/containers/test_sampler_pub_result.py
@@ -0,0 +1,104 @@
+# 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.
+
+
+"""Unit tests for SamplerPubResult."""
+
+from test import QiskitTestCase
+
+import numpy as np
+
+from qiskit.primitives.containers import BitArray, DataBin, SamplerPubResult
+
+
+class SamplerPubResultCase(QiskitTestCase):
+    """Test the SamplerPubResult class."""
+
+    def test_construction(self):
+        """Test that the constructor works."""
+        ba = BitArray.from_samples(["00", "11"], 2)
+        counts = {"00": 1, "11": 1}
+        data_bin = DataBin(a=ba, b=ba)
+        pub_result = SamplerPubResult(data_bin)
+        self.assertEqual(pub_result.data.a.get_counts(), counts)
+        self.assertEqual(pub_result.data.b.get_counts(), counts)
+        self.assertEqual(pub_result.metadata, {})
+
+        pub_result = SamplerPubResult(data_bin, {"x": 1})
+        self.assertEqual(pub_result.data.a.get_counts(), counts)
+        self.assertEqual(pub_result.data.b.get_counts(), counts)
+        self.assertEqual(pub_result.metadata, {"x": 1})
+
+    def test_repr(self):
+        """Test that the repr doesn't fail"""
+        # we are primarily interested in making sure some future change doesn't cause the repr to
+        # raise an error. it is more sensible for humans to detect a deficiency in the formatting
+        # itself, should one be uncovered
+        ba = BitArray.from_samples(["00", "11"], 2)
+        data_bin = DataBin(a=ba, b=ba)
+        self.assertTrue(repr(SamplerPubResult(data_bin)).startswith("SamplerPubResult"))
+        self.assertTrue(repr(SamplerPubResult(data_bin, {"x": 1})).startswith("SamplerPubResult"))
+
+    def test_join_data_failures(self):
+        """Test the join_data() failure mechanisms work."""
+
+        result = SamplerPubResult(DataBin())
+        with self.assertRaisesRegex(ValueError, "No entry exists in the data bin"):
+            result.join_data()
+
+        alpha = BitArray.from_samples(["00", "11"], 2)
+        beta = BitArray.from_samples(["010", "101"], 3)
+        result = SamplerPubResult(DataBin(alpha=alpha, beta=beta))
+        with self.assertRaisesRegex(ValueError, "An empty name list is given"):
+            result.join_data([])
+
+        alpha = BitArray.from_samples(["00", "11"], 2)
+        beta = BitArray.from_samples(["010", "101"], 3)
+        result = SamplerPubResult(DataBin(alpha=alpha, beta=beta))
+        with self.assertRaisesRegex(ValueError, "Name 'foo' does not exist"):
+            result.join_data(["alpha", "foo"])
+
+        alpha = BitArray.from_samples(["00", "11"], 2)
+        beta = np.empty((2,))
+        result = SamplerPubResult(DataBin(alpha=alpha, beta=beta))
+        with self.assertRaisesRegex(TypeError, "Data comes from incompatible types"):
+            result.join_data()
+
+        alpha = np.empty((2,))
+        beta = BitArray.from_samples(["00", "11"], 2)
+        result = SamplerPubResult(DataBin(alpha=alpha, beta=beta))
+        with self.assertRaisesRegex(TypeError, "Data comes from incompatible types"):
+            result.join_data()
+
+        result = SamplerPubResult(DataBin(alpha=1, beta={}))
+        with self.assertRaisesRegex(TypeError, "Data comes from incompatible types"):
+            result.join_data()
+
+    def test_join_data_bit_array_default(self):
+        """Test the join_data() method with no arguments and bit arrays."""
+        alpha = BitArray.from_samples(["00", "11"], 2)
+        beta = BitArray.from_samples(["010", "101"], 3)
+        data_bin = DataBin(alpha=alpha, beta=beta)
+        result = SamplerPubResult(data_bin)
+
+        gamma = result.join_data()
+        self.assertEqual(list(gamma.get_bitstrings()), ["01000", "10111"])
+
+    def test_join_data_ndarray_default(self):
+        """Test the join_data() method with no arguments and ndarrays."""
+        alpha = np.linspace(0, 1, 30).reshape((2, 3, 5))
+        beta = np.linspace(0, 1, 12).reshape((2, 3, 2))
+        data_bin = DataBin(alpha=alpha, beta=beta, shape=(2, 3))
+        result = SamplerPubResult(data_bin)
+
+        gamma = result.join_data()
+        np.testing.assert_allclose(gamma, np.concatenate([alpha, beta], axis=2))

From cc7c47dc7b435eee9055cd2ec41f3e0b28481b15 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Fri, 3 May 2024 06:32:37 -0400
Subject: [PATCH 065/179] Fix composition of multiple virtual permutation
 layouts (#12335)

This commit fixes the composition of multiple virtual permutation
layouts in StarPreRouting and ElidePermutations. Both of these passes
set a virtual permutation layout and if both running a pipeline the
layouts need to be composed together to track the permutation.
Previously the logic for doing this was incorrect (and also had a syntax
error). This commit fixes this so that you can run the two passes
together (along with any other future passes that do a similar thing).
This was spun out from #12196 as a standalone bugfix.
---
 .../passes/optimization/elide_permutations.py |  6 ++--
 .../passes/routing/star_prerouting.py         |  6 ++--
 .../transpiler/test_elide_permutations.py     | 28 +++++++++++++++++++
 3 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/qiskit/transpiler/passes/optimization/elide_permutations.py b/qiskit/transpiler/passes/optimization/elide_permutations.py
index d9704ff0518b..ca6902f15b43 100644
--- a/qiskit/transpiler/passes/optimization/elide_permutations.py
+++ b/qiskit/transpiler/passes/optimization/elide_permutations.py
@@ -105,8 +105,10 @@ def _apply_mapping(qargs):
             self.property_set["original_qubit_indices"] = input_qubit_mapping
 
         new_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
-        if current_layout := self.property_set["virtual_permutation_layout"] is not None:
-            self.property_set["virtual_permutation_layout"] = current_layout.compose(new_layout)
+        if current_layout := self.property_set["virtual_permutation_layout"]:
+            self.property_set["virtual_permutation_layout"] = new_layout.compose(
+                current_layout.inverse(dag.qubits, dag.qubits), dag.qubits
+            )
         else:
             self.property_set["virtual_permutation_layout"] = new_layout
         return new_dag
diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py
index 8e278471295a..4b27749c6dad 100644
--- a/qiskit/transpiler/passes/routing/star_prerouting.py
+++ b/qiskit/transpiler/passes/routing/star_prerouting.py
@@ -267,8 +267,10 @@ def run(self, dag):
             self.property_set["original_qubit_indices"] = input_qubit_mapping
 
         new_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
-        if current_layout := self.property_set["virtual_permutation_layout"] is not None:
-            self.property_set["virtual_permutation_layout"] = current_layout.compose(new_layout)
+        if current_layout := self.property_set["virtual_permutation_layout"]:
+            self.property_set["virtual_permutation_layout"] = new_layout.compose(
+                current_layout.inverse(dag.qubits, dag.qubits), dag.qubits
+            )
         else:
             self.property_set["virtual_permutation_layout"] = new_layout
 
diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py
index 6e0379f4bc5b..c96b5ca32d89 100644
--- a/test/python/transpiler/test_elide_permutations.py
+++ b/test/python/transpiler/test_elide_permutations.py
@@ -17,6 +17,7 @@
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.library.generalized_gates import PermutationGate
 from qiskit.transpiler.passes.optimization.elide_permutations import ElidePermutations
+from qiskit.transpiler.passes.routing import StarPreRouting
 from qiskit.circuit.controlflow import IfElseOp
 from qiskit.quantum_info import Operator
 from qiskit.transpiler.coupling import CouplingMap
@@ -426,6 +427,33 @@ def test_unitary_equivalence_routing_and_basis_translation(self):
             qc_with_ancillas.append(qc, [0, 1, 2, 3, 4])
             self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc_with_ancillas)))
 
+    def test_unitary_equivalence_virtual_permutation_layout_composition(self):
+        """Test on a larger example that includes routing and basis translation."""
+
+        qc = QuantumCircuit(5)
+        qc.h(0)
+        qc.swap(0, 2)
+        qc.cx(0, 1)
+        qc.swap(1, 0)
+        qc.cx(0, 1)
+        qc.cx(0, 2)
+        qc.cx(0, 3)
+        qc.cx(0, 4)
+        qc.append(PermutationGate([0, 2, 1]), [0, 1, 2])
+        qc.h(1)
+
+        with self.subTest("with coupling map"):
+            spm = generate_preset_pass_manager(
+                optimization_level=3,
+                seed_transpiler=1234,
+                coupling_map=CouplingMap.from_line(5),
+                basis_gates=["u", "cz"],
+            )
+            spm.init += ElidePermutations()
+            spm.init += StarPreRouting()
+            res = spm.run(qc)
+            self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc)))
+
 
 if __name__ == "__main__":
     unittest.main()

From 68f4b528948ddefd874a754ac8db6865ea964bb5 Mon Sep 17 00:00:00 2001
From: John Lapeyre 
Date: Fri, 3 May 2024 09:49:35 -0400
Subject: [PATCH 066/179] Adapt crates/qasm3 to work with recent versions of
 openqasm3_parser (#12087)

* Adapt crates/qasm3 to work with recent versions of openqasm3_parser

This commit adds no new features or capabilities to the importer. But it brings
the importer up to date with the latest version of openqasm3_parser as a preliminary
step in improving the importer.

The biggest change in openqasm3_parser is in handling stdgates.inc.  Upon encountering
`include "stdgates.inc" openqasm3_parser reads no file, but rather adds symbols to the
symbol table for gates in the standard library.

The function `convert_asg` in the importer calls oq3_semantics::symbols::SymbolTable.gates
which returns a `Vec` of information about each "declared" gate. The information is the
gate's name and signature and SymbolID, which is sufficient to do everything the importer
could do before this commit.

Encountering `Stmt::GateDefinition` now is a no-op rather than an error.  (This was
previously called `Stmt::GateDeclaration`, but importantly it is really a definition.)

How the standard library, and gates in general, are handled will continue to evolve.

A behavioral difference between this commit and its parent: Before if the importer
encountered a gate call before the corresponding definition an error would be raised. With
the current commit, the importer behaves as if all gate definitions were moved to the top
of the OQ3 program. However, the error will still be found by the parser so that the
importer never will begin processing statements.

The importer depends on a particular branch of a copy of openqasm3_parser. When
the commit is ready to merge, we will release a version of openqasm3_parser and
depend instead on that release.

See https://github.com/openqasm/openqasm/pull/517

* Remove unnecessary conversion `name.to_string()`

Response to reviewer comment https://github.com/Qiskit/qiskit/pull/12087/files#r1586949717

* Remove check for U gate in map_gate_ids

* This requires modifying the external parser crate.
* Depend temporarily on the branch of the parser with this modification.
* Make another change required by other upstream improvments.
* Cargo config files are changed because of above changes.

* Return error returned by map_gate_ids in convert_asg

Previously this error was ignored. This would almost certainly cause
an error later. But better to do it at the correct place.

* Remove superfluous call to `iter()`

* Depend on published oq3_semantics 0.6.0

* Fix cargo lock file

---------

Co-authored-by: Matthew Treinish 
---
 Cargo.lock                | 216 ++++++++++++++++----------------------
 crates/qasm3/Cargo.toml   |   2 +-
 crates/qasm3/src/build.rs |  84 ++++++++-------
 crates/qasm3/src/expr.rs  |   4 +-
 4 files changed, 134 insertions(+), 172 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index dee434e870a4..50903b3811af 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,17 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "ahash"
 version = "0.8.11"
@@ -106,7 +117,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -265,7 +276,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -285,7 +296,7 @@ checksum = "60d08acb9849f7fb4401564f251be5a526829183a3645a90197dea8e786cf3ae"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -509,14 +520,17 @@ name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
 
 [[package]]
 name = "hashbrown"
-version = "0.14.3"
+version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "allocator-api2",
  "rayon",
 ]
@@ -550,7 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "rayon",
 ]
 
@@ -606,9 +620,9 @@ checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.154"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
 
 [[package]]
 name = "libm"
@@ -618,9 +632,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
 
 [[package]]
 name = "lock_api"
-version = "0.4.11"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -771,9 +785,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "oq3_lexer"
-version = "0.0.7"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4e867d2797100b8068715e26566a5567c598424d7eddf7118c6b38bc3b15633"
+checksum = "0de2f0f9d48042c12f82b2550808378718627e108fc3f6adf63e02e5293541a3"
 dependencies = [
  "unicode-properties",
  "unicode-xid",
@@ -781,9 +795,9 @@ dependencies = [
 
 [[package]]
 name = "oq3_parser"
-version = "0.0.7"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf260dea71b56b405d091d476748c1f9b0a4d22b4ec9af416e002e2df25613f9"
+checksum = "e69b215426a4a2a023fd62cca4436c633ba0ab39ee260aca875ac60321b3704b"
 dependencies = [
  "drop_bomb",
  "oq3_lexer",
@@ -792,12 +806,12 @@ dependencies = [
 
 [[package]]
 name = "oq3_semantics"
-version = "0.0.7"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5ba220b91ff849190d53b296711774f761b7e06744b16a9c8f19fc2fb37de47"
+checksum = "3e15e9cee54e92fb1b3aaa42556b0bd76c8c1c10912a7d6798f43dfc3afdcb0d"
 dependencies = [
  "boolenum",
- "hashbrown 0.14.3",
+ "hashbrown 0.12.3",
  "oq3_source_file",
  "oq3_syntax",
  "rowan",
@@ -805,9 +819,9 @@ dependencies = [
 
 [[package]]
 name = "oq3_source_file"
-version = "0.0.7"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a81fd0c1c100ad8d7a23711c897791d693c3f5b1f3d044cb8c5770766f819c"
+checksum = "4f65243cc4807c600c544a21db6c17544c53aa2bc034b3eccf388251cc6530e7"
 dependencies = [
  "ariadne",
  "oq3_syntax",
@@ -815,9 +829,9 @@ dependencies = [
 
 [[package]]
 name = "oq3_syntax"
-version = "0.0.7"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7da2ef9a591d77eee43e972e79fc95c218545e5e79b93738d20479d8d7627ec"
+checksum = "a8c3d637a7db9ddb3811719db8a466bd4960ea668df4b2d14043a1b0038465b0"
 dependencies = [
  "cov-mark",
  "either",
@@ -829,6 +843,7 @@ dependencies = [
  "ra_ap_stdx",
  "rowan",
  "rustc-hash",
+ "rustversion",
  "smol_str",
  "triomphe",
  "xshell",
@@ -836,9 +851,9 @@ dependencies = [
 
 [[package]]
 name = "parking_lot"
-version = "0.12.1"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -846,15 +861,15 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.9"
+version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
@@ -865,9 +880,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
 
 [[package]]
 name = "pest"
-version = "2.7.9"
+version = "2.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
+checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
 dependencies = [
  "memchr",
  "thiserror",
@@ -876,9 +891,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.9"
+version = "2.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
+checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
 dependencies = [
  "pest",
  "pest_generator",
@@ -886,22 +901,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.9"
+version = "2.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
+checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.9"
+version = "2.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
+checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
 dependencies = [
  "once_cell",
  "pest",
@@ -994,7 +1009,7 @@ checksum = "d315b3197b780e4873bc0e11251cb56a33f65a6032a3d39b8d1405c255513766"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1017,7 +1032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
 dependencies = [
  "cfg-if",
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "indexmap 2.2.6",
  "indoc",
  "libc",
@@ -1062,7 +1077,7 @@ dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1075,18 +1090,18 @@ dependencies = [
  "proc-macro2",
  "pyo3-build-config",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
 name = "qiskit-accelerate"
 version = "1.1.0"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "approx",
  "faer",
  "faer-ext",
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "indexmap 2.2.6",
  "itertools 0.12.1",
  "ndarray",
@@ -1109,7 +1124,7 @@ dependencies = [
 name = "qiskit-circuit"
 version = "1.1.0"
 dependencies = [
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "pyo3",
 ]
 
@@ -1128,7 +1143,7 @@ dependencies = [
 name = "qiskit-qasm2"
 version = "1.1.0"
 dependencies = [
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "pyo3",
  "qiskit-circuit",
 ]
@@ -1137,7 +1152,7 @@ dependencies = [
 name = "qiskit-qasm3"
 version = "1.1.0"
 dependencies = [
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "indexmap 2.2.6",
  "oq3_semantics",
  "pyo3",
@@ -1275,11 +1290,11 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
 
 [[package]]
 name = "redox_syscall"
-version = "0.4.1"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.5.0",
 ]
 
 [[package]]
@@ -1289,7 +1304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49"
 dependencies = [
  "countme",
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "memoffset",
  "rustc-hash",
  "text-size",
@@ -1301,15 +1316,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
+[[package]]
+name = "rustversion"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
+
 [[package]]
 name = "rustworkx-core"
 version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "529027dfaa8125aa61bb7736ae9484f41e8544f448af96918c8da6b1def7f57b"
 dependencies = [
- "ahash",
+ "ahash 0.8.11",
  "fixedbitset",
- "hashbrown 0.14.3",
+ "hashbrown 0.14.5",
  "indexmap 2.2.6",
  "num-traits",
  "petgraph",
@@ -1343,22 +1364,22 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
 
 [[package]]
 name = "serde"
-version = "1.0.198"
+version = "1.0.200"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.198"
+version = "1.0.200"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1400,9 +1421,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.59"
+version = "2.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1437,22 +1458,22 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
 
 [[package]]
 name = "thiserror"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
 
 [[package]]
@@ -1487,9 +1508,9 @@ checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.11"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
 
 [[package]]
 name = "unicode-xid"
@@ -1543,11 +1564,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.6"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
 dependencies = [
- "winapi",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1604,21 +1625,6 @@ dependencies = [
  "windows_x86_64_msvc 0.42.2",
 ]
 
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
 [[package]]
 name = "windows-targets"
 version = "0.52.5"
@@ -1641,12 +1647,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.52.5"
@@ -1659,12 +1659,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.52.5"
@@ -1677,12 +1671,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
 [[package]]
 name = "windows_i686_gnu"
 version = "0.52.5"
@@ -1701,12 +1689,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
 [[package]]
 name = "windows_i686_msvc"
 version = "0.52.5"
@@ -1719,12 +1701,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.52.5"
@@ -1737,12 +1713,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.52.5"
@@ -1755,12 +1725,6 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.52.5"
@@ -1805,5 +1769,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.59",
+ "syn 2.0.60",
 ]
diff --git a/crates/qasm3/Cargo.toml b/crates/qasm3/Cargo.toml
index a8e20d13d58c..4dd0d977786e 100644
--- a/crates/qasm3/Cargo.toml
+++ b/crates/qasm3/Cargo.toml
@@ -13,4 +13,4 @@ doctest = false
 pyo3.workspace = true
 indexmap.workspace = true
 hashbrown.workspace = true
-oq3_semantics = "0.0.7"
+oq3_semantics = "0.6.0"
diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs
index 154cd391252a..2f817187625d 100644
--- a/crates/qasm3/src/build.rs
+++ b/crates/qasm3/src/build.rs
@@ -226,42 +226,32 @@ impl BuilderState {
         self.qc.append(py, instruction).map(|_| ())
     }
 
-    fn define_gate(
-        &mut self,
-        _py: Python,
-        ast_symbols: &SymbolTable,
-        decl: &asg::GateDeclaration,
-    ) -> PyResult<()> {
-        let name_id = decl
-            .name()
-            .as_ref()
-            .map_err(|err| QASM3ImporterError::new_err(format!("internal error: {:?}", err)))?;
-        let name_symbol = &ast_symbols[name_id];
-        let pygate = self.pygates.get(name_symbol.name()).ok_or_else(|| {
-            QASM3ImporterError::new_err(format!(
-                "can't handle non-built-in gate: '{}'",
-                name_symbol.name()
-            ))
-        })?;
-        let defined_num_params = decl.params().as_ref().map_or(0, Vec::len);
-        let defined_num_qubits = decl.qubits().len();
-        if pygate.num_params() != defined_num_params {
-            return Err(QASM3ImporterError::new_err(format!(
-                "given constructor for '{}' expects {} parameters, but is defined as taking {}",
-                pygate.name(),
-                pygate.num_params(),
-                defined_num_params,
-            )));
-        }
-        if pygate.num_qubits() != defined_num_qubits {
-            return Err(QASM3ImporterError::new_err(format!(
-                "given constructor for '{}' expects {} qubits, but is defined as taking {}",
-                pygate.name(),
-                pygate.num_qubits(),
-                defined_num_qubits,
-            )));
+    // Map gates in the symbol table to Qiskit gates in the standard library.
+    // Encountering any gates not in the standard library results in raising an exception.
+    // Gates mapped via CustomGates will not raise an exception.
+    fn map_gate_ids(&mut self, _py: Python, ast_symbols: &SymbolTable) -> PyResult<()> {
+        for (name, name_id, defined_num_params, defined_num_qubits) in ast_symbols.gates() {
+            let pygate = self.pygates.get(name).ok_or_else(|| {
+                QASM3ImporterError::new_err(format!("can't handle non-built-in gate: '{}'", name))
+            })?;
+            if pygate.num_params() != defined_num_params {
+                return Err(QASM3ImporterError::new_err(format!(
+                    "given constructor for '{}' expects {} parameters, but is defined as taking {}",
+                    pygate.name(),
+                    pygate.num_params(),
+                    defined_num_params,
+                )));
+            }
+            if pygate.num_qubits() != defined_num_qubits {
+                return Err(QASM3ImporterError::new_err(format!(
+                    "given constructor for '{}' expects {} qubits, but is defined as taking {}",
+                    pygate.name(),
+                    pygate.num_qubits(),
+                    defined_num_qubits,
+                )));
+            }
+            self.symbols.gates.insert(name_id.clone(), pygate.clone());
         }
-        self.symbols.gates.insert(name_id.clone(), pygate.clone());
         Ok(())
     }
 
@@ -377,37 +367,45 @@ pub fn convert_asg(
         pygates: gate_constructors,
         module,
     };
+
+    state.map_gate_ids(py, ast_symbols)?;
+
     for statement in program.stmts().iter() {
         match statement {
             asg::Stmt::GateCall(call) => state.call_gate(py, ast_symbols, call)?,
             asg::Stmt::DeclareClassical(decl) => state.declare_classical(py, ast_symbols, decl)?,
             asg::Stmt::DeclareQuantum(decl) => state.declare_quantum(py, ast_symbols, decl)?,
-            asg::Stmt::GateDeclaration(decl) => state.define_gate(py, ast_symbols, decl)?,
+            // We ignore gate definitions because the only information we can currently use
+            // from them is extracted with `SymbolTable::gates` via `map_gate_ids`.
+            asg::Stmt::GateDefinition(_) => (),
             asg::Stmt::Barrier(barrier) => state.apply_barrier(py, ast_symbols, barrier)?,
             asg::Stmt::Assignment(assignment) => state.assign(py, ast_symbols, assignment)?,
-            asg::Stmt::Alias
+            asg::Stmt::Alias(_)
             | asg::Stmt::AnnotatedStmt(_)
             | asg::Stmt::Block(_)
             | asg::Stmt::Box
             | asg::Stmt::Break
             | asg::Stmt::Cal
             | asg::Stmt::Continue
-            | asg::Stmt::Def
+            | asg::Stmt::DeclareHardwareQubit(_)
             | asg::Stmt::DefCal
-            | asg::Stmt::Delay
+            | asg::Stmt::DefStmt(_)
+            | asg::Stmt::Delay(_)
             | asg::Stmt::End
             | asg::Stmt::ExprStmt(_)
             | asg::Stmt::Extern
-            | asg::Stmt::For
+            | asg::Stmt::ForStmt(_)
             | asg::Stmt::GPhaseCall(_)
-            | asg::Stmt::IODeclaration
             | asg::Stmt::If(_)
             | asg::Stmt::Include(_)
+            | asg::Stmt::InputDeclaration(_)
+            | asg::Stmt::ModifiedGPhaseCall(_)
             | asg::Stmt::NullStmt
             | asg::Stmt::OldStyleDeclaration
+            | asg::Stmt::OutputDeclaration(_)
             | asg::Stmt::Pragma(_)
-            | asg::Stmt::Reset
-            | asg::Stmt::Return
+            | asg::Stmt::Reset(_)
+            | asg::Stmt::SwitchCaseStmt(_)
             | asg::Stmt::While(_) => {
                 return Err(QASM3ImporterError::new_err(format!(
                     "this statement is not yet handled during OpenQASM 3 import: {:?}",
diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs
index d16bd53add05..e912aecdb875 100644
--- a/crates/qasm3/src/expr.rs
+++ b/crates/qasm3/src/expr.rs
@@ -244,11 +244,11 @@ pub fn eval_qarg(
     qarg: &asg::GateOperand,
 ) -> PyResult {
     match qarg {
-        asg::GateOperand::Identifier(iden) => broadcast_bits_for_identifier(
+        asg::GateOperand::Identifier(symbol) => broadcast_bits_for_identifier(
             py,
             &our_symbols.qubits,
             &our_symbols.qregs,
-            iden.symbol().as_ref().unwrap(),
+            symbol.as_ref().unwrap(),
         ),
         asg::GateOperand::IndexedIdentifier(indexed) => {
             let iden_symbol = indexed.identifier().as_ref().unwrap();

From 7882eb108ba935585d96b808ac12c916790a8a5b Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Fri, 3 May 2024 09:50:46 -0400
Subject: [PATCH 067/179] Set version number to 1.1.0rc1 for first release
 candidate (#12328)

For the 1.1.0 release we're going to push release candidates prior to
the final to enable testing before we cut the final release. In
preparation for tagging the first release candidate this commit updates
the version string to indicate it's a release candidate. This commit
should be the final commit on main for 1.1.0rc1 and what gets tagged as
1.1.0rc1 post-merge.
---
 docs/conf.py                                                  | 4 ++--
 qiskit/VERSION.txt                                            | 2 +-
 .../abstract-commutation-analysis-3518129e91a33599.yaml       | 0
 .../add-annotated-arg-to-power-4afe90e89fa50f5a.yaml          | 0
 .../{ => 1.1}/add-backend-estimator-v2-26cf14a3612bb81a.yaml  | 0
 .../{ => 1.1}/add-backend-sampler-v2-5e40135781eebc7f.yaml    | 0
 .../{ => 1.1}/add-bitarray-utilities-c85261138d5a1a97.yaml    | 0
 .../add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml        | 0
 .../add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml  | 0
 .../notes/{ => 1.1}/add-elide-swaps-b0a4c373c9af1efd.yaml     | 0
 .../{ => 1.1}/add-linear-plugin-options-b8a0ffe70dfe1676.yaml | 0
 .../add-run-all-plugins-option-ba8806a269e5713c.yaml          | 0
 .../{ => 1.1}/add-scheduler-warnings-da6968a39fd8e6e7.yaml    | 0
 ...-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml | 0
 .../added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml      | 0
 .../notes/{ => 1.1}/classical-store-e64ee1286219a862.yaml     | 0
 .../{ => 1.1}/commutation-checker-utf8-47b13b78a40af196.yaml  | 0
 ...tive-cancellation-preset-passmanager-c137ce516a10eae5.yaml | 0
 .../{ => 1.1}/databin-construction-72ec041075410cb2.yaml      | 0
 .../notes/{ => 1.1}/databin-mapping-45d24d71f9bb4eda.yaml     | 0
 .../notes/{ => 1.1}/deprecate-3.8-a9db071fa3c85b1a.yaml       | 0
 .../{ => 1.1}/deprecate_providerV1-ba17d7b4639d1cc5.yaml      | 0
 .../notes/{ => 1.1}/expr-bitshift-index-e9cfc6ea8729ef5e.yaml | 0
 .../notes/{ => 1.1}/faster-lie-trotter-ba8f6dd84fe4cae4.yaml  | 0
 .../fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml  | 0
 .../fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml  | 0
 .../fix-control-flow-fold-minus-one-f2af168a5313385f.yaml     | 0
 .../fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml    | 0
 .../fix-custom-transpile-constraints-5defa36d540d1608.yaml    | 0
 .../{ => 1.1}/fix-equivalence-setentry-5a30b0790666fcf2.yaml  | 0
 ...ix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml | 0
 .../fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml  | 0
 ...ix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml | 0
 .../{ => 1.1}/fix-mcx-mcp-performance-b00040804b47b200.yaml   | 0
 .../fix-missing-qubit-properties-35137aa5250d9368.yaml        | 0
 .../{ => 1.1}/fix-passmanager-reuse-151877e1905d49df.yaml     | 0
 .../notes/{ => 1.1}/fix-pauli-evolve-ecr-and-name-bugs.yaml   | 0
 ...fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml | 0
 .../notes/{ => 1.1}/fix-pub-coerce-5d13700e15126421.yaml      | 0
 .../fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml | 0
 .../fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml       | 0
 .../{ => 1.1}/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml      | 0
 .../{ => 1.1}/fix-scheduling-units-59477912b47d3dc1.yaml      | 0
 ...x-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml | 0
 .../notes/{ => 1.1}/fix_soft_compare-3f4148aab3a4606b.yaml    | 0
 .../notes/{ => 1.1}/fixes_10852-e197344c5f44b4f1.yaml         | 0
 .../notes/{ => 1.1}/fixes_11212-d6de3c007ce6d697.yaml         | 0
 .../notes/{ => 1.1}/followup_11468-61c6181e62531796.yaml      | 0
 .../notes/{ => 1.1}/histogram-style-03807965c3cc2e8a.yaml     | 0
 .../notes/{ => 1.1}/layout-compose-0b9a9a72359638d8.yaml      | 0
 .../notes/{ => 1.1}/macos-arm64-tier-1-c5030f009be6adcb.yaml  | 0
 .../notes/{ => 1.1}/nlocal-perf-3b8ebd9be1b2f4b3.yaml         | 0
 releasenotes/notes/{ => 1.1}/numpy-2.0-2f3e35bd42c48518.yaml  | 0
 .../notes/{ => 1.1}/obs-array-coerce-0d-28b192fb3d004d4a.yaml | 0
 .../operator-from-circuit-bugfix-5dab5993526a2b0a.yaml        | 0
 .../notes/{ => 1.1}/optimization-level2-2c8c1488173aed31.yaml | 0
 ...timize-annotated-conjugate-reduction-656438d3642f27dc.yaml | 0
 .../notes/{ => 1.1}/parameter-hash-eq-645f9de55aa78d02.yaml   | 0
 ...signment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml | 0
 .../notes/{ => 1.1}/pauli-apply-layout-cdcbc1bce724a150.yaml  | 0
 .../public-noncommutation-graph-dd31c931b7045a4f.yaml         | 0
 ..._manager_compat_with_ParameterVector-7d31395fd4019827.yaml | 0
 .../qasm3-parameter-gate-clash-34ef7b0383849a78.yaml          | 0
 .../qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml      | 0
 .../quantumcircuit-append-copy-8a9b71ad4b789490.yaml          | 0
 releasenotes/notes/{ => 1.1}/qv-perf-be76290f472e4777.yaml    | 0
 .../notes/{ => 1.1}/remove-final-reset-488247c01c4e147d.yaml  | 0
 .../{ => 1.1}/removed_deprecated_0.21-741d08a01a7ed527.yaml   | 0
 .../{ => 1.1}/reverse-permutation-lnn-409a07c7f6d0eed9.yaml   | 0
 .../rework-inst-durations-passes-28c78401682e22c0.yaml        | 0
 .../rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml     | 0
 .../notes/{ => 1.1}/rust-two-qubit-weyl-ec551f3f9c812124.yaml | 0
 .../notes/{ => 1.1}/sampler-pub-result-e64e7de1bae2d35e.yaml  | 0
 .../show_idle_and_show_barrier-6e77e1f9d6f55599.yaml          | 0
 .../notes/{ => 1.1}/spo-to-matrix-26445a791e24f62a.yaml       | 0
 .../notes/{ => 1.1}/star-prerouting-0998b59880c20cef.yaml     | 0
 .../{ => 1.1}/update-gate-dictionary-c0c017be67bb2f29.yaml    | 0
 .../{ => 1.1}/use-target-in-transpile-7c04b14549a11f40.yaml   | 0
 78 files changed, 3 insertions(+), 3 deletions(-)
 rename releasenotes/notes/{ => 1.1}/abstract-commutation-analysis-3518129e91a33599.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-backend-estimator-v2-26cf14a3612bb81a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-backend-sampler-v2-5e40135781eebc7f.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-bitarray-utilities-c85261138d5a1a97.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-elide-swaps-b0a4c373c9af1efd.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-linear-plugin-options-b8a0ffe70dfe1676.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-run-all-plugins-option-ba8806a269e5713c.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-scheduler-warnings-da6968a39fd8e6e7.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/classical-store-e64ee1286219a862.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/commutation-checker-utf8-47b13b78a40af196.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/commutative-cancellation-preset-passmanager-c137ce516a10eae5.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/databin-construction-72ec041075410cb2.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/databin-mapping-45d24d71f9bb4eda.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/deprecate-3.8-a9db071fa3c85b1a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/deprecate_providerV1-ba17d7b4639d1cc5.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/expr-bitshift-index-e9cfc6ea8729ef5e.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/faster-lie-trotter-ba8f6dd84fe4cae4.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-control-flow-fold-minus-one-f2af168a5313385f.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-custom-transpile-constraints-5defa36d540d1608.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-equivalence-setentry-5a30b0790666fcf2.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-mcx-mcp-performance-b00040804b47b200.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-missing-qubit-properties-35137aa5250d9368.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-passmanager-reuse-151877e1905d49df.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-pauli-evolve-ecr-and-name-bugs.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-pub-coerce-5d13700e15126421.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-scheduling-units-59477912b47d3dc1.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fix_soft_compare-3f4148aab3a4606b.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fixes_10852-e197344c5f44b4f1.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/fixes_11212-d6de3c007ce6d697.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/followup_11468-61c6181e62531796.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/histogram-style-03807965c3cc2e8a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/layout-compose-0b9a9a72359638d8.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/macos-arm64-tier-1-c5030f009be6adcb.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/nlocal-perf-3b8ebd9be1b2f4b3.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/numpy-2.0-2f3e35bd42c48518.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/obs-array-coerce-0d-28b192fb3d004d4a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/optimization-level2-2c8c1488173aed31.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/parameter-hash-eq-645f9de55aa78d02.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/pauli-apply-layout-cdcbc1bce724a150.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/public-noncommutation-graph-dd31c931b7045a4f.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/quantumcircuit-append-copy-8a9b71ad4b789490.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/qv-perf-be76290f472e4777.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/remove-final-reset-488247c01c4e147d.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/removed_deprecated_0.21-741d08a01a7ed527.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/reverse-permutation-lnn-409a07c7f6d0eed9.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/rework-inst-durations-passes-28c78401682e22c0.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/rust-two-qubit-weyl-ec551f3f9c812124.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/sampler-pub-result-e64e7de1bae2d35e.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/spo-to-matrix-26445a791e24f62a.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/star-prerouting-0998b59880c20cef.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/update-gate-dictionary-c0c017be67bb2f29.yaml (100%)
 rename releasenotes/notes/{ => 1.1}/use-target-in-transpile-7c04b14549a11f40.yaml (100%)

diff --git a/docs/conf.py b/docs/conf.py
index 4a79b543ed78..7081b7d4ed9a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -32,7 +32,7 @@
 # The short X.Y version
 version = "1.1"
 # The full version, including alpha/beta/rc tags
-release = "1.1.0"
+release = "1.1.0rc1"
 
 language = "en"
 
@@ -178,7 +178,7 @@ def linkcode_resolve(domain, info):
     if "qiskit" not in module_name:
         return None
 
-    try: 
+    try:
         module = importlib.import_module(module_name)
     except ModuleNotFoundError:
         return None
diff --git a/qiskit/VERSION.txt b/qiskit/VERSION.txt
index 9084fa2f716a..686366e4bb8a 100644
--- a/qiskit/VERSION.txt
+++ b/qiskit/VERSION.txt
@@ -1 +1 @@
-1.1.0
+1.1.0rc1
diff --git a/releasenotes/notes/abstract-commutation-analysis-3518129e91a33599.yaml b/releasenotes/notes/1.1/abstract-commutation-analysis-3518129e91a33599.yaml
similarity index 100%
rename from releasenotes/notes/abstract-commutation-analysis-3518129e91a33599.yaml
rename to releasenotes/notes/1.1/abstract-commutation-analysis-3518129e91a33599.yaml
diff --git a/releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml b/releasenotes/notes/1.1/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml
similarity index 100%
rename from releasenotes/notes/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml
rename to releasenotes/notes/1.1/add-annotated-arg-to-power-4afe90e89fa50f5a.yaml
diff --git a/releasenotes/notes/add-backend-estimator-v2-26cf14a3612bb81a.yaml b/releasenotes/notes/1.1/add-backend-estimator-v2-26cf14a3612bb81a.yaml
similarity index 100%
rename from releasenotes/notes/add-backend-estimator-v2-26cf14a3612bb81a.yaml
rename to releasenotes/notes/1.1/add-backend-estimator-v2-26cf14a3612bb81a.yaml
diff --git a/releasenotes/notes/add-backend-sampler-v2-5e40135781eebc7f.yaml b/releasenotes/notes/1.1/add-backend-sampler-v2-5e40135781eebc7f.yaml
similarity index 100%
rename from releasenotes/notes/add-backend-sampler-v2-5e40135781eebc7f.yaml
rename to releasenotes/notes/1.1/add-backend-sampler-v2-5e40135781eebc7f.yaml
diff --git a/releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml b/releasenotes/notes/1.1/add-bitarray-utilities-c85261138d5a1a97.yaml
similarity index 100%
rename from releasenotes/notes/add-bitarray-utilities-c85261138d5a1a97.yaml
rename to releasenotes/notes/1.1/add-bitarray-utilities-c85261138d5a1a97.yaml
diff --git a/releasenotes/notes/add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml b/releasenotes/notes/1.1/add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml
similarity index 100%
rename from releasenotes/notes/add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml
rename to releasenotes/notes/1.1/add-ctrl_state-mcp-parameter-b23562aa7047665a.yaml
diff --git a/releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml b/releasenotes/notes/1.1/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml
similarity index 100%
rename from releasenotes/notes/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml
rename to releasenotes/notes/1.1/add-elide-permutations-to-pipeline-077dad03bd55ab9c.yaml
diff --git a/releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml b/releasenotes/notes/1.1/add-elide-swaps-b0a4c373c9af1efd.yaml
similarity index 100%
rename from releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml
rename to releasenotes/notes/1.1/add-elide-swaps-b0a4c373c9af1efd.yaml
diff --git a/releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml b/releasenotes/notes/1.1/add-linear-plugin-options-b8a0ffe70dfe1676.yaml
similarity index 100%
rename from releasenotes/notes/add-linear-plugin-options-b8a0ffe70dfe1676.yaml
rename to releasenotes/notes/1.1/add-linear-plugin-options-b8a0ffe70dfe1676.yaml
diff --git a/releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml b/releasenotes/notes/1.1/add-run-all-plugins-option-ba8806a269e5713c.yaml
similarity index 100%
rename from releasenotes/notes/add-run-all-plugins-option-ba8806a269e5713c.yaml
rename to releasenotes/notes/1.1/add-run-all-plugins-option-ba8806a269e5713c.yaml
diff --git a/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml b/releasenotes/notes/1.1/add-scheduler-warnings-da6968a39fd8e6e7.yaml
similarity index 100%
rename from releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml
rename to releasenotes/notes/1.1/add-scheduler-warnings-da6968a39fd8e6e7.yaml
diff --git a/releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml b/releasenotes/notes/1.1/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml
similarity index 100%
rename from releasenotes/notes/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml
rename to releasenotes/notes/1.1/add-use-dag-flag-two-qubit-basis-decomposer-024a9ced9833289c.yaml
diff --git a/releasenotes/notes/added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml b/releasenotes/notes/1.1/added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml
similarity index 100%
rename from releasenotes/notes/added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml
rename to releasenotes/notes/1.1/added-parameter-ctrl_state-mcx-816dcd80e459a5ed.yaml
diff --git a/releasenotes/notes/classical-store-e64ee1286219a862.yaml b/releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml
similarity index 100%
rename from releasenotes/notes/classical-store-e64ee1286219a862.yaml
rename to releasenotes/notes/1.1/classical-store-e64ee1286219a862.yaml
diff --git a/releasenotes/notes/commutation-checker-utf8-47b13b78a40af196.yaml b/releasenotes/notes/1.1/commutation-checker-utf8-47b13b78a40af196.yaml
similarity index 100%
rename from releasenotes/notes/commutation-checker-utf8-47b13b78a40af196.yaml
rename to releasenotes/notes/1.1/commutation-checker-utf8-47b13b78a40af196.yaml
diff --git a/releasenotes/notes/commutative-cancellation-preset-passmanager-c137ce516a10eae5.yaml b/releasenotes/notes/1.1/commutative-cancellation-preset-passmanager-c137ce516a10eae5.yaml
similarity index 100%
rename from releasenotes/notes/commutative-cancellation-preset-passmanager-c137ce516a10eae5.yaml
rename to releasenotes/notes/1.1/commutative-cancellation-preset-passmanager-c137ce516a10eae5.yaml
diff --git a/releasenotes/notes/databin-construction-72ec041075410cb2.yaml b/releasenotes/notes/1.1/databin-construction-72ec041075410cb2.yaml
similarity index 100%
rename from releasenotes/notes/databin-construction-72ec041075410cb2.yaml
rename to releasenotes/notes/1.1/databin-construction-72ec041075410cb2.yaml
diff --git a/releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml b/releasenotes/notes/1.1/databin-mapping-45d24d71f9bb4eda.yaml
similarity index 100%
rename from releasenotes/notes/databin-mapping-45d24d71f9bb4eda.yaml
rename to releasenotes/notes/1.1/databin-mapping-45d24d71f9bb4eda.yaml
diff --git a/releasenotes/notes/deprecate-3.8-a9db071fa3c85b1a.yaml b/releasenotes/notes/1.1/deprecate-3.8-a9db071fa3c85b1a.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-3.8-a9db071fa3c85b1a.yaml
rename to releasenotes/notes/1.1/deprecate-3.8-a9db071fa3c85b1a.yaml
diff --git a/releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml b/releasenotes/notes/1.1/deprecate_providerV1-ba17d7b4639d1cc5.yaml
similarity index 100%
rename from releasenotes/notes/deprecate_providerV1-ba17d7b4639d1cc5.yaml
rename to releasenotes/notes/1.1/deprecate_providerV1-ba17d7b4639d1cc5.yaml
diff --git a/releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml b/releasenotes/notes/1.1/expr-bitshift-index-e9cfc6ea8729ef5e.yaml
similarity index 100%
rename from releasenotes/notes/expr-bitshift-index-e9cfc6ea8729ef5e.yaml
rename to releasenotes/notes/1.1/expr-bitshift-index-e9cfc6ea8729ef5e.yaml
diff --git a/releasenotes/notes/faster-lie-trotter-ba8f6dd84fe4cae4.yaml b/releasenotes/notes/1.1/faster-lie-trotter-ba8f6dd84fe4cae4.yaml
similarity index 100%
rename from releasenotes/notes/faster-lie-trotter-ba8f6dd84fe4cae4.yaml
rename to releasenotes/notes/1.1/faster-lie-trotter-ba8f6dd84fe4cae4.yaml
diff --git a/releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml b/releasenotes/notes/1.1/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml
similarity index 100%
rename from releasenotes/notes/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml
rename to releasenotes/notes/1.1/fix-backend-primitives-performance-1409b08ccc2a5ce9.yaml
diff --git a/releasenotes/notes/fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml b/releasenotes/notes/1.1/fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml
similarity index 100%
rename from releasenotes/notes/fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml
rename to releasenotes/notes/1.1/fix-control-flow-convert-to-target-ae838418a7ad2a20.yaml
diff --git a/releasenotes/notes/fix-control-flow-fold-minus-one-f2af168a5313385f.yaml b/releasenotes/notes/1.1/fix-control-flow-fold-minus-one-f2af168a5313385f.yaml
similarity index 100%
rename from releasenotes/notes/fix-control-flow-fold-minus-one-f2af168a5313385f.yaml
rename to releasenotes/notes/1.1/fix-control-flow-fold-minus-one-f2af168a5313385f.yaml
diff --git a/releasenotes/notes/fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml b/releasenotes/notes/1.1/fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml
similarity index 100%
rename from releasenotes/notes/fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml
rename to releasenotes/notes/1.1/fix-custom-pulse-qobj-conversion-5d6041b36356cfd1.yaml
diff --git a/releasenotes/notes/fix-custom-transpile-constraints-5defa36d540d1608.yaml b/releasenotes/notes/1.1/fix-custom-transpile-constraints-5defa36d540d1608.yaml
similarity index 100%
rename from releasenotes/notes/fix-custom-transpile-constraints-5defa36d540d1608.yaml
rename to releasenotes/notes/1.1/fix-custom-transpile-constraints-5defa36d540d1608.yaml
diff --git a/releasenotes/notes/fix-equivalence-setentry-5a30b0790666fcf2.yaml b/releasenotes/notes/1.1/fix-equivalence-setentry-5a30b0790666fcf2.yaml
similarity index 100%
rename from releasenotes/notes/fix-equivalence-setentry-5a30b0790666fcf2.yaml
rename to releasenotes/notes/1.1/fix-equivalence-setentry-5a30b0790666fcf2.yaml
diff --git a/releasenotes/notes/fix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml b/releasenotes/notes/1.1/fix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml
similarity index 100%
rename from releasenotes/notes/fix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml
rename to releasenotes/notes/1.1/fix-evolved-operator-ansatz-empty-ops-bf8ecfae8f1e1001.yaml
diff --git a/releasenotes/notes/fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml b/releasenotes/notes/1.1/fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml
similarity index 100%
rename from releasenotes/notes/fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml
rename to releasenotes/notes/1.1/fix-instruction-repeat-conditional-dfe4d7ced54a7bb6.yaml
diff --git a/releasenotes/notes/fix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml b/releasenotes/notes/1.1/fix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml
similarity index 100%
rename from releasenotes/notes/fix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml
rename to releasenotes/notes/1.1/fix-inverse-cancellation-self-inverse-e09a5553331e1b0b.yaml
diff --git a/releasenotes/notes/fix-mcx-mcp-performance-b00040804b47b200.yaml b/releasenotes/notes/1.1/fix-mcx-mcp-performance-b00040804b47b200.yaml
similarity index 100%
rename from releasenotes/notes/fix-mcx-mcp-performance-b00040804b47b200.yaml
rename to releasenotes/notes/1.1/fix-mcx-mcp-performance-b00040804b47b200.yaml
diff --git a/releasenotes/notes/fix-missing-qubit-properties-35137aa5250d9368.yaml b/releasenotes/notes/1.1/fix-missing-qubit-properties-35137aa5250d9368.yaml
similarity index 100%
rename from releasenotes/notes/fix-missing-qubit-properties-35137aa5250d9368.yaml
rename to releasenotes/notes/1.1/fix-missing-qubit-properties-35137aa5250d9368.yaml
diff --git a/releasenotes/notes/fix-passmanager-reuse-151877e1905d49df.yaml b/releasenotes/notes/1.1/fix-passmanager-reuse-151877e1905d49df.yaml
similarity index 100%
rename from releasenotes/notes/fix-passmanager-reuse-151877e1905d49df.yaml
rename to releasenotes/notes/1.1/fix-passmanager-reuse-151877e1905d49df.yaml
diff --git a/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml b/releasenotes/notes/1.1/fix-pauli-evolve-ecr-and-name-bugs.yaml
similarity index 100%
rename from releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml
rename to releasenotes/notes/1.1/fix-pauli-evolve-ecr-and-name-bugs.yaml
diff --git a/releasenotes/notes/fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml b/releasenotes/notes/1.1/fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml
similarity index 100%
rename from releasenotes/notes/fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml
rename to releasenotes/notes/1.1/fix-performance-scaling-num-bits-qpy-37b5109a40cccc54.yaml
diff --git a/releasenotes/notes/fix-pub-coerce-5d13700e15126421.yaml b/releasenotes/notes/1.1/fix-pub-coerce-5d13700e15126421.yaml
similarity index 100%
rename from releasenotes/notes/fix-pub-coerce-5d13700e15126421.yaml
rename to releasenotes/notes/1.1/fix-pub-coerce-5d13700e15126421.yaml
diff --git a/releasenotes/notes/fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml b/releasenotes/notes/1.1/fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml
similarity index 100%
rename from releasenotes/notes/fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml
rename to releasenotes/notes/1.1/fix-pulse-builder-default-alingment-52f81224d90c21e2.yaml
diff --git a/releasenotes/notes/fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml b/releasenotes/notes/1.1/fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml
similarity index 100%
rename from releasenotes/notes/fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml
rename to releasenotes/notes/1.1/fix-pulse-parameter-formatter-2ee3fb91efb2794c.yaml
diff --git a/releasenotes/notes/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml b/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml
similarity index 100%
rename from releasenotes/notes/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml
rename to releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml
diff --git a/releasenotes/notes/fix-scheduling-units-59477912b47d3dc1.yaml b/releasenotes/notes/1.1/fix-scheduling-units-59477912b47d3dc1.yaml
similarity index 100%
rename from releasenotes/notes/fix-scheduling-units-59477912b47d3dc1.yaml
rename to releasenotes/notes/1.1/fix-scheduling-units-59477912b47d3dc1.yaml
diff --git a/releasenotes/notes/fix-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml b/releasenotes/notes/1.1/fix-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml
similarity index 100%
rename from releasenotes/notes/fix-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml
rename to releasenotes/notes/1.1/fix-transpile-control-flow-no-hardware-7c00ad733a569bb9.yaml
diff --git a/releasenotes/notes/fix_soft_compare-3f4148aab3a4606b.yaml b/releasenotes/notes/1.1/fix_soft_compare-3f4148aab3a4606b.yaml
similarity index 100%
rename from releasenotes/notes/fix_soft_compare-3f4148aab3a4606b.yaml
rename to releasenotes/notes/1.1/fix_soft_compare-3f4148aab3a4606b.yaml
diff --git a/releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml b/releasenotes/notes/1.1/fixes_10852-e197344c5f44b4f1.yaml
similarity index 100%
rename from releasenotes/notes/fixes_10852-e197344c5f44b4f1.yaml
rename to releasenotes/notes/1.1/fixes_10852-e197344c5f44b4f1.yaml
diff --git a/releasenotes/notes/fixes_11212-d6de3c007ce6d697.yaml b/releasenotes/notes/1.1/fixes_11212-d6de3c007ce6d697.yaml
similarity index 100%
rename from releasenotes/notes/fixes_11212-d6de3c007ce6d697.yaml
rename to releasenotes/notes/1.1/fixes_11212-d6de3c007ce6d697.yaml
diff --git a/releasenotes/notes/followup_11468-61c6181e62531796.yaml b/releasenotes/notes/1.1/followup_11468-61c6181e62531796.yaml
similarity index 100%
rename from releasenotes/notes/followup_11468-61c6181e62531796.yaml
rename to releasenotes/notes/1.1/followup_11468-61c6181e62531796.yaml
diff --git a/releasenotes/notes/histogram-style-03807965c3cc2e8a.yaml b/releasenotes/notes/1.1/histogram-style-03807965c3cc2e8a.yaml
similarity index 100%
rename from releasenotes/notes/histogram-style-03807965c3cc2e8a.yaml
rename to releasenotes/notes/1.1/histogram-style-03807965c3cc2e8a.yaml
diff --git a/releasenotes/notes/layout-compose-0b9a9a72359638d8.yaml b/releasenotes/notes/1.1/layout-compose-0b9a9a72359638d8.yaml
similarity index 100%
rename from releasenotes/notes/layout-compose-0b9a9a72359638d8.yaml
rename to releasenotes/notes/1.1/layout-compose-0b9a9a72359638d8.yaml
diff --git a/releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml b/releasenotes/notes/1.1/macos-arm64-tier-1-c5030f009be6adcb.yaml
similarity index 100%
rename from releasenotes/notes/macos-arm64-tier-1-c5030f009be6adcb.yaml
rename to releasenotes/notes/1.1/macos-arm64-tier-1-c5030f009be6adcb.yaml
diff --git a/releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml b/releasenotes/notes/1.1/nlocal-perf-3b8ebd9be1b2f4b3.yaml
similarity index 100%
rename from releasenotes/notes/nlocal-perf-3b8ebd9be1b2f4b3.yaml
rename to releasenotes/notes/1.1/nlocal-perf-3b8ebd9be1b2f4b3.yaml
diff --git a/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml b/releasenotes/notes/1.1/numpy-2.0-2f3e35bd42c48518.yaml
similarity index 100%
rename from releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml
rename to releasenotes/notes/1.1/numpy-2.0-2f3e35bd42c48518.yaml
diff --git a/releasenotes/notes/obs-array-coerce-0d-28b192fb3d004d4a.yaml b/releasenotes/notes/1.1/obs-array-coerce-0d-28b192fb3d004d4a.yaml
similarity index 100%
rename from releasenotes/notes/obs-array-coerce-0d-28b192fb3d004d4a.yaml
rename to releasenotes/notes/1.1/obs-array-coerce-0d-28b192fb3d004d4a.yaml
diff --git a/releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml b/releasenotes/notes/1.1/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml
similarity index 100%
rename from releasenotes/notes/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml
rename to releasenotes/notes/1.1/operator-from-circuit-bugfix-5dab5993526a2b0a.yaml
diff --git a/releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml b/releasenotes/notes/1.1/optimization-level2-2c8c1488173aed31.yaml
similarity index 100%
rename from releasenotes/notes/optimization-level2-2c8c1488173aed31.yaml
rename to releasenotes/notes/1.1/optimization-level2-2c8c1488173aed31.yaml
diff --git a/releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml b/releasenotes/notes/1.1/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml
similarity index 100%
rename from releasenotes/notes/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml
rename to releasenotes/notes/1.1/optimize-annotated-conjugate-reduction-656438d3642f27dc.yaml
diff --git a/releasenotes/notes/parameter-hash-eq-645f9de55aa78d02.yaml b/releasenotes/notes/1.1/parameter-hash-eq-645f9de55aa78d02.yaml
similarity index 100%
rename from releasenotes/notes/parameter-hash-eq-645f9de55aa78d02.yaml
rename to releasenotes/notes/1.1/parameter-hash-eq-645f9de55aa78d02.yaml
diff --git a/releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml b/releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml
similarity index 100%
rename from releasenotes/notes/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml
rename to releasenotes/notes/1.1/parameter_assignment_by_name_for_pulse_schedules-3a27bbbbf235fb9e.yaml
diff --git a/releasenotes/notes/pauli-apply-layout-cdcbc1bce724a150.yaml b/releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml
similarity index 100%
rename from releasenotes/notes/pauli-apply-layout-cdcbc1bce724a150.yaml
rename to releasenotes/notes/1.1/pauli-apply-layout-cdcbc1bce724a150.yaml
diff --git a/releasenotes/notes/public-noncommutation-graph-dd31c931b7045a4f.yaml b/releasenotes/notes/1.1/public-noncommutation-graph-dd31c931b7045a4f.yaml
similarity index 100%
rename from releasenotes/notes/public-noncommutation-graph-dd31c931b7045a4f.yaml
rename to releasenotes/notes/1.1/public-noncommutation-graph-dd31c931b7045a4f.yaml
diff --git a/releasenotes/notes/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml b/releasenotes/notes/1.1/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml
similarity index 100%
rename from releasenotes/notes/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml
rename to releasenotes/notes/1.1/pulse_parameter_manager_compat_with_ParameterVector-7d31395fd4019827.yaml
diff --git a/releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml b/releasenotes/notes/1.1/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml
similarity index 100%
rename from releasenotes/notes/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml
rename to releasenotes/notes/1.1/qasm3-parameter-gate-clash-34ef7b0383849a78.yaml
diff --git a/releasenotes/notes/qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml b/releasenotes/notes/1.1/qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml
similarity index 100%
rename from releasenotes/notes/qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml
rename to releasenotes/notes/1.1/qcstyle-bug-custom-style-dicts-22deab6c602ccd6a.yaml
diff --git a/releasenotes/notes/quantumcircuit-append-copy-8a9b71ad4b789490.yaml b/releasenotes/notes/1.1/quantumcircuit-append-copy-8a9b71ad4b789490.yaml
similarity index 100%
rename from releasenotes/notes/quantumcircuit-append-copy-8a9b71ad4b789490.yaml
rename to releasenotes/notes/1.1/quantumcircuit-append-copy-8a9b71ad4b789490.yaml
diff --git a/releasenotes/notes/qv-perf-be76290f472e4777.yaml b/releasenotes/notes/1.1/qv-perf-be76290f472e4777.yaml
similarity index 100%
rename from releasenotes/notes/qv-perf-be76290f472e4777.yaml
rename to releasenotes/notes/1.1/qv-perf-be76290f472e4777.yaml
diff --git a/releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml b/releasenotes/notes/1.1/remove-final-reset-488247c01c4e147d.yaml
similarity index 100%
rename from releasenotes/notes/remove-final-reset-488247c01c4e147d.yaml
rename to releasenotes/notes/1.1/remove-final-reset-488247c01c4e147d.yaml
diff --git a/releasenotes/notes/removed_deprecated_0.21-741d08a01a7ed527.yaml b/releasenotes/notes/1.1/removed_deprecated_0.21-741d08a01a7ed527.yaml
similarity index 100%
rename from releasenotes/notes/removed_deprecated_0.21-741d08a01a7ed527.yaml
rename to releasenotes/notes/1.1/removed_deprecated_0.21-741d08a01a7ed527.yaml
diff --git a/releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml b/releasenotes/notes/1.1/reverse-permutation-lnn-409a07c7f6d0eed9.yaml
similarity index 100%
rename from releasenotes/notes/reverse-permutation-lnn-409a07c7f6d0eed9.yaml
rename to releasenotes/notes/1.1/reverse-permutation-lnn-409a07c7f6d0eed9.yaml
diff --git a/releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml b/releasenotes/notes/1.1/rework-inst-durations-passes-28c78401682e22c0.yaml
similarity index 100%
rename from releasenotes/notes/rework-inst-durations-passes-28c78401682e22c0.yaml
rename to releasenotes/notes/1.1/rework-inst-durations-passes-28c78401682e22c0.yaml
diff --git a/releasenotes/notes/rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml b/releasenotes/notes/1.1/rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml
similarity index 100%
rename from releasenotes/notes/rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml
rename to releasenotes/notes/1.1/rust-two-qubit-basis-decomposer-329ead588fa7526d.yaml
diff --git a/releasenotes/notes/rust-two-qubit-weyl-ec551f3f9c812124.yaml b/releasenotes/notes/1.1/rust-two-qubit-weyl-ec551f3f9c812124.yaml
similarity index 100%
rename from releasenotes/notes/rust-two-qubit-weyl-ec551f3f9c812124.yaml
rename to releasenotes/notes/1.1/rust-two-qubit-weyl-ec551f3f9c812124.yaml
diff --git a/releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml b/releasenotes/notes/1.1/sampler-pub-result-e64e7de1bae2d35e.yaml
similarity index 100%
rename from releasenotes/notes/sampler-pub-result-e64e7de1bae2d35e.yaml
rename to releasenotes/notes/1.1/sampler-pub-result-e64e7de1bae2d35e.yaml
diff --git a/releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml b/releasenotes/notes/1.1/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml
similarity index 100%
rename from releasenotes/notes/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml
rename to releasenotes/notes/1.1/show_idle_and_show_barrier-6e77e1f9d6f55599.yaml
diff --git a/releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml b/releasenotes/notes/1.1/spo-to-matrix-26445a791e24f62a.yaml
similarity index 100%
rename from releasenotes/notes/spo-to-matrix-26445a791e24f62a.yaml
rename to releasenotes/notes/1.1/spo-to-matrix-26445a791e24f62a.yaml
diff --git a/releasenotes/notes/star-prerouting-0998b59880c20cef.yaml b/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml
similarity index 100%
rename from releasenotes/notes/star-prerouting-0998b59880c20cef.yaml
rename to releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml
diff --git a/releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml b/releasenotes/notes/1.1/update-gate-dictionary-c0c017be67bb2f29.yaml
similarity index 100%
rename from releasenotes/notes/update-gate-dictionary-c0c017be67bb2f29.yaml
rename to releasenotes/notes/1.1/update-gate-dictionary-c0c017be67bb2f29.yaml
diff --git a/releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml b/releasenotes/notes/1.1/use-target-in-transpile-7c04b14549a11f40.yaml
similarity index 100%
rename from releasenotes/notes/use-target-in-transpile-7c04b14549a11f40.yaml
rename to releasenotes/notes/1.1/use-target-in-transpile-7c04b14549a11f40.yaml

From 27bd5e79f56a396933e030d178cebcf972320c34 Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Sun, 5 May 2024 11:23:31 -0400
Subject: [PATCH 068/179] Link to `qiskit.synthesis.unitary.aqc` on API index
 page (#12331)

* Link to `qiskit.synthesis.unitary.aqc` on API index page

* Add to index page
---
 docs/apidoc/index.rst                        | 1 +
 docs/apidoc/qiskit.synthesis.unitary.aqc.rst | 6 ++++++
 qiskit/synthesis/__init__.py                 | 7 +------
 3 files changed, 8 insertions(+), 6 deletions(-)
 create mode 100644 docs/apidoc/qiskit.synthesis.unitary.aqc.rst

diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst
index 89a2a6bb9a62..3a6c1b04cfdf 100644
--- a/docs/apidoc/index.rst
+++ b/docs/apidoc/index.rst
@@ -25,6 +25,7 @@ API Reference
    pulse
    scheduler
    synthesis
+   qiskit.synthesis.unitary.aqc
    primitives
    qasm2
    qasm3
diff --git a/docs/apidoc/qiskit.synthesis.unitary.aqc.rst b/docs/apidoc/qiskit.synthesis.unitary.aqc.rst
new file mode 100644
index 000000000000..5f3219a40dba
--- /dev/null
+++ b/docs/apidoc/qiskit.synthesis.unitary.aqc.rst
@@ -0,0 +1,6 @@
+.. _qiskit-synthesis_unitary_aqc:
+
+.. automodule:: qiskit.synthesis.unitary.aqc
+   :no-members:
+   :no-inherited-members:
+   :no-special-members:
diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py
index 49e7885d5096..b46c8eac5459 100644
--- a/qiskit/synthesis/__init__.py
+++ b/qiskit/synthesis/__init__.py
@@ -99,12 +99,7 @@
 
 .. autofunction:: qs_decomposition
 
-The Approximate Quantum Compiler is available here:
-
-.. autosummary::
-    :toctree: ../stubs/
-
-    qiskit.synthesis.unitary.aqc
+The Approximate Quantum Compiler is available as the module :mod:`qiskit.synthesis.unitary.aqc`.
 
 One-Qubit Synthesis
 ===================

From c1d728a9c8c51eaf12b4f118107ffc7cbeda956d Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Mon, 6 May 2024 02:52:31 -0400
Subject: [PATCH 069/179] Bump main branch version post 1.1.0rc1 tag (#12338)

Now that the first release candidate for the 1.1.0 release has been
tagged, the stable branch for the 1.1 series has been created and we
can start developing the 1.1.0 release on main. This commit bumps all
the version strings from 1.1.0 to 1.2.0 (and the backport branch for
mergify to stable/1.1) accordingly to differentiate the main branch
from 1.1.*.
---
 .mergify.yml       |  2 +-
 Cargo.lock         | 10 +++++-----
 Cargo.toml         |  2 +-
 docs/conf.py       |  4 ++--
 qiskit/VERSION.txt |  2 +-
 5 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/.mergify.yml b/.mergify.yml
index 87a3438930f5..10b6d2022256 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -6,4 +6,4 @@ pull_request_rules:
     actions:
       backport:
         branches:
-          - stable/1.0
+          - stable/1.1
diff --git a/Cargo.lock b/Cargo.lock
index 50903b3811af..84e0e0090663 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1095,7 +1095,7 @@ dependencies = [
 
 [[package]]
 name = "qiskit-accelerate"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "ahash 0.8.11",
  "approx",
@@ -1122,7 +1122,7 @@ dependencies = [
 
 [[package]]
 name = "qiskit-circuit"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "hashbrown 0.14.5",
  "pyo3",
@@ -1130,7 +1130,7 @@ dependencies = [
 
 [[package]]
 name = "qiskit-pyext"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "pyo3",
  "qiskit-accelerate",
@@ -1141,7 +1141,7 @@ dependencies = [
 
 [[package]]
 name = "qiskit-qasm2"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "hashbrown 0.14.5",
  "pyo3",
@@ -1150,7 +1150,7 @@ dependencies = [
 
 [[package]]
 name = "qiskit-qasm3"
-version = "1.1.0"
+version = "1.2.0"
 dependencies = [
  "hashbrown 0.14.5",
  "indexmap 2.2.6",
diff --git a/Cargo.toml b/Cargo.toml
index 9c4af6260bed..2827b2206f4d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ members = ["crates/*"]
 resolver = "2"
 
 [workspace.package]
-version = "1.1.0"
+version = "1.2.0"
 edition = "2021"
 rust-version = "1.70"  # Keep in sync with README.md and rust-toolchain.toml.
 license = "Apache-2.0"
diff --git a/docs/conf.py b/docs/conf.py
index 7081b7d4ed9a..b35f5ca64d66 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -30,9 +30,9 @@
 author = "Qiskit Development Team"
 
 # The short X.Y version
-version = "1.1"
+version = "1.2"
 # The full version, including alpha/beta/rc tags
-release = "1.1.0rc1"
+release = "1.2.0"
 
 language = "en"
 
diff --git a/qiskit/VERSION.txt b/qiskit/VERSION.txt
index 686366e4bb8a..26aaba0e8663 100644
--- a/qiskit/VERSION.txt
+++ b/qiskit/VERSION.txt
@@ -1 +1 @@
-1.1.0rc1
+1.2.0

From 87a0396095ae348cd5fb90d8cef805674edfb8fc Mon Sep 17 00:00:00 2001
From: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com>
Date: Mon, 6 May 2024 12:23:21 +0530
Subject: [PATCH 070/179] Fix typo under 'pull request checklist' heading
 (#12340)

---
 CONTRIBUTING.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1ea3dc9f60f3..c75b51750017 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -183,8 +183,8 @@ please ensure that:
    If your pull request is adding a new class, function, or module that is
    intended to be user facing ensure that you've also added those to a
    documentation `autosummary` index to include it in the api documentation.
-3. If it makes sense for your change that you have added new tests that
-   cover the changes.
+3. If you are of the opinion that the modifications you made warrant additional tests,
+   feel free to include them
 4. Ensure that if your change has an end user facing impact (new feature,
    deprecation, removal etc) that you have added a reno release note for that
    change and that the PR is tagged for the changelog.

From 1412a5e5ab1d131fb42502338aa1ae49c64cd6c2 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Mon, 6 May 2024 02:54:40 -0400
Subject: [PATCH 071/179] Removing lint rule
 use-implicit-booleaness-not-comparison and updates (#12218)

* removing lint rule use-implicit-booleaness-not-comparison and updates

* fix merge mistake
---
 pyproject.toml                                    | 1 -
 qiskit/pulse/instruction_schedule_map.py          | 2 +-
 qiskit/synthesis/linear_phase/cnot_phase_synth.py | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 97ccde21d1b7..9c7094827c8a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -231,7 +231,6 @@ disable = [
     "unnecessary-lambda-assignment",
     "unspecified-encoding",
     "unsupported-assignment-operation",
-    "use-implicit-booleaness-not-comparison",
 ]
 
 enable = [
diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py
index 3d3767509f27..decda15c9947 100644
--- a/qiskit/pulse/instruction_schedule_map.py
+++ b/qiskit/pulse/instruction_schedule_map.py
@@ -250,7 +250,7 @@ def add(
 
         # validation of target qubit
         qubits = _to_tuple(qubits)
-        if qubits == ():
+        if not qubits:
             raise PulseError(f"Cannot add definition {instruction} with no target qubits.")
 
         # generate signature
diff --git a/qiskit/synthesis/linear_phase/cnot_phase_synth.py b/qiskit/synthesis/linear_phase/cnot_phase_synth.py
index b107241310f9..25320029ef54 100644
--- a/qiskit/synthesis/linear_phase/cnot_phase_synth.py
+++ b/qiskit/synthesis/linear_phase/cnot_phase_synth.py
@@ -123,7 +123,7 @@ def synth_cnot_phase_aam(
 
     # Implementation of the pseudo-code (Algorithm 1) in the aforementioned paper
     sta.append([cnots, range_list, epsilon])
-    while sta != []:
+    while sta:
         [cnots, ilist, qubit] = sta.pop()
         if cnots == []:
             continue

From f39c9070692e3955ada776e1c3fc537fe00fefd1 Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Mon, 6 May 2024 08:49:40 -0400
Subject: [PATCH 072/179] Improve header hierarchy for API module pages
 (#12325)

* Improve header hierarchy for API module pages

* Tweaks

* Review feedback
---
 qiskit/assembler/__init__.py                 |  15 +--
 qiskit/circuit/__init__.py                   |   4 +-
 qiskit/circuit/classical/types/__init__.py   |   9 +-
 qiskit/circuit/classicalfunction/__init__.py |   1 +
 qiskit/circuit/library/__init__.py           |  22 +---
 qiskit/converters/__init__.py                |  19 ++-
 qiskit/providers/__init__.py                 |  29 ++---
 qiskit/providers/basic_provider/__init__.py  |  25 +---
 qiskit/providers/fake_provider/__init__.py   |   2 +-
 qiskit/providers/models/__init__.py          |   4 +-
 qiskit/qpy/__init__.py                       | 118 +++++++++----------
 qiskit/result/__init__.py                    |   6 +
 qiskit/scheduler/__init__.py                 |  11 +-
 qiskit/scheduler/methods/__init__.py         |   9 +-
 qiskit/transpiler/passes/__init__.py         |   6 +-
 15 files changed, 129 insertions(+), 151 deletions(-)

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..ff36550967d8 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.
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/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/providers/__init__.py b/qiskit/providers/__init__.py
index 19d300c9bbf6..b0ebc942523c 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,8 @@ 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
-----------
+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 +483,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 +514,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 +611,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 +639,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/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/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/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/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/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/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/

From 2c418aa9b359645642230c1cecb5122e9d01de56 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 6 May 2024 13:27:12 +0000
Subject: [PATCH 073/179] Bump num-traits from 0.2.18 to 0.2.19 (#12349)

Bumps [num-traits](https://github.com/rust-num/num-traits) from 0.2.18 to 0.2.19.
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.18...num-traits-0.2.19)

---
updated-dependencies:
- dependency-name: num-traits
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 84e0e0090663..c8b22ed79172 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -754,9 +754,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",

From c062dd6cf22558f630903d530728f2e1b60dacc3 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Mon, 6 May 2024 15:08:19 -0400
Subject: [PATCH 074/179] Fix Rustfmt and clippy with latest rust stable
 (#12339)

* Fix Rustfmt and clippy with latest rust stable

Running `cargo fmt` with a newer version of rust than what we use in CI
(1.77.2 and 1.78.0 locally for me) is triggering the formatting updates
in this commit. To ensure developers don't have to worry about commiting
an accidental formatting change in their commits this commit proactively
makes the change. Similarly the recent Rust 1.78 release included new
clippy rules which are flagging some small issues that our MSRV of
clippy doesn't have. This commit also fixes these as the suggestions
are good and are compatible with our MSRV of 1.70.

* Rename deprecated config file name

* Remove dead code
---
 .cargo/{config => config.toml}           |  0
 crates/accelerate/src/sparse_pauli_op.rs |  4 +++-
 crates/circuit/src/circuit_data.rs       |  2 +-
 crates/qasm3/src/circuit.rs              | 10 ----------
 4 files changed, 4 insertions(+), 12 deletions(-)
 rename .cargo/{config => config.toml} (100%)

diff --git a/.cargo/config b/.cargo/config.toml
similarity index 100%
rename from .cargo/config
rename to .cargo/config.toml
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/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)
             }

From 6ff0eb54f88cdbe98275872b8ec9d65f76948bb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?=
 <57907331+ElePT@users.noreply.github.com>
Date: Tue, 7 May 2024 11:49:31 +0200
Subject: [PATCH 075/179] Add standalone test file for Clifford synthesis
 functions (#12347)

* Convert synthesis imports to runtime imports to avoid cyclic import errors

* Set copy=False in append

* Move clifford synthesis tests to separate file in test/synthesis.

* Add random_clifford_circuit to qiskit.circuit.random

* Remove pylint disable

* Fix reno
---
 .../generalized_gates/linear_function.py      |   5 +-
 .../circuit/library/generalized_gates/uc.py   |   2 +-
 .../library/generalized_gates/unitary.py      |  18 +-
 .../n_local/evolved_operator_ansatz.py        |   5 +-
 qiskit/circuit/library/pauli_evolution.py     |  10 +-
 qiskit/circuit/random/__init__.py             |   2 +-
 qiskit/circuit/random/utils.py                |  71 ++++++-
 .../quantum_info/operators/dihedral/random.py |   6 +-
 .../clifford/clifford_decompose_bm.py         |   6 +-
 .../clifford/clifford_decompose_layers.py     |  18 +-
 ...random-clifford-util-5358041208729988.yaml |  14 ++
 .../operators/symplectic/test_clifford.py     | 187 +-----------------
 .../synthesis/test_clifford_sythesis.py       | 118 +++++++++++
 13 files changed, 248 insertions(+), 214 deletions(-)
 create mode 100644 releasenotes/notes/add-random-clifford-util-5358041208729988.yaml
 create mode 100644 test/python/synthesis/test_clifford_sythesis.py

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/uc.py b/qiskit/circuit/library/generalized_gates/uc.py
index f54567123e02..6e6a1db95ca3 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
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/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/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/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/synthesis/clifford/clifford_decompose_bm.py b/qiskit/synthesis/clifford/clifford_decompose_bm.py
index cbc54f16bb0c..50ffcb743162 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()
 
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/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/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/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)

From 6a872a8e48e642631c73ee37ec4b949d98386d16 Mon Sep 17 00:00:00 2001
From: Alexander Ivrii 
Date: Tue, 7 May 2024 17:59:41 +0300
Subject: [PATCH 076/179] docstring fixes (#12358)

---
 qiskit/quantum_info/operators/symplectic/pauli.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index e1bcfa29ebcb..1ccecc04a6c0 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

From 0f7424cb144cf985107273780b809091ceff0432 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 8 May 2024 00:31:25 +0100
Subject: [PATCH 077/179] Bump num-bigint from 0.4.4 to 0.4.5 (#12357)

Bumps [num-bigint](https://github.com/rust-num/num-bigint) from 0.4.4 to 0.4.5.
- [Changelog](https://github.com/rust-num/num-bigint/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.4...num-bigint-0.4.5)

---
updated-dependencies:
- dependency-name: num-bigint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index c8b22ed79172..33114124e1ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -724,11 +724,10 @@ 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",
 ]

From b5c5179a0da209911dae69916bc1454def851a3e Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Wed, 8 May 2024 01:37:22 -0400
Subject: [PATCH 078/179] fix indentation in XXPlusYYGate docstring (#12365)

* fix indentation in XXPlusYYGate docstring

* break long lines
---
 .../library/standard_gates/xx_plus_yy.py      | 26 ++++++++++---------
 1 file changed, 14 insertions(+), 12 deletions(-)

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__(

From 8b94fc36df05a9f5b2150e7a8ac1c36dca3286d1 Mon Sep 17 00:00:00 2001
From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Date: Wed, 8 May 2024 12:13:16 +0300
Subject: [PATCH 079/179] Replace initialization method by Isometry in
 StatePreparation (#12178)

* replace initializetion method by Isometry in StatePreparation

* add names to QuantumRegister and QuantumCircuit

* update docs to the new reference

* remove old initialization code based on Shende et al

* fix reference in docs

* add release notes

* fix lint errors

* fix references in docs

* add a benchmark for state preparation

* update circuit name following review
---
 .../library/data_preparation/initializer.py   |   8 +
 .../data_preparation/state_preparation.py     | 208 ++----------------
 .../library/generalized_gates/isometry.py     |  16 +-
 .../generalized_gates/mcg_up_to_diagonal.py   |   8 +-
 .../circuit/library/generalized_gates/uc.py   |   4 +-
 .../library/generalized_gates/uc_pauli_rot.py |   4 +-
 ...lgorithm-by-isometry-41f9ffa58f72ece5.yaml |   7 +
 test/benchmarks/statepreparation.py           |  66 ++++++
 8 files changed, 114 insertions(+), 207 deletions(-)
 create mode 100644 releasenotes/notes/replace-initialization-algorithm-by-isometry-41f9ffa58f72ece5.yaml
 create mode 100644 test/benchmarks/statepreparation.py

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/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/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 6e6a1db95ca3..c81494da3eec 100644
--- a/qiskit/circuit/library/generalized_gates/uc.py
+++ b/qiskit/circuit/library/generalized_gates/uc.py
@@ -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/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/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

From be1d24a739b3e78316ef592ebd06188bb9824d2c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 8 May 2024 12:24:10 +0000
Subject: [PATCH 080/179] Bump num-complex from 0.4.5 to 0.4.6 (#12368)

Bumps [num-complex](https://github.com/rust-num/num-complex) from 0.4.5 to 0.4.6.
- [Changelog](https://github.com/rust-num/num-complex/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-complex/compare/num-complex-0.4.5...num-complex-0.4.6)

---
updated-dependencies:
- dependency-name: num-complex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 33114124e1ed..b6915167f9c8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -734,9 +734,9 @@ dependencies = [
 
 [[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",

From 87c14cb2bf9cfd9ae12985de994b6c3a31edfa61 Mon Sep 17 00:00:00 2001
From: Derek Miner 
Date: Wed, 8 May 2024 09:42:18 -0500
Subject: [PATCH 081/179] Add new method for single bitstring target when
 measuring stabilizerstate probabilities_dict (#12147)

* added new method, deterministic helper unction

added new method probabilities_dict_from_bitstrings, moved logic from probabilities_dict to probabilities_dict_from_bitstrings with ability to pass target, probabilities_dict will pass a target of None.

* Adding test timing, fixed algorithm for determining if should get probability for qubit

Added some timing of the tests to determine if the version with a specfic target runs faster then the non-targetted version (which must calculate all 2^n possibilities for stabilizer state test_probablities_dict_single_qubit. When given a target it can skip branches that were not wanting to be calculated

* Adding more tests, fixing deterministic issues with bad target

* Prob of targets with 0.0 probability

* target calc changes

* Simpler way to get all targets

* Need to improve performance with multiple targets

* Simplified the probabilities_dict_from_bitstrings method

simplified the method probabilities_dict_from_bitstrings, less checking was needed, simpler handling of no target value passed in

* Adding tests for targets

* Added tests to test_probablities_dict_qubits

* Add performance boost check when using targets for test_probablities_dict_qubits

* Added Caching to prevent duplicate branch calculations

In stabilizerstate, added the ability to cache previously calculated branches so they do not get recalculated when attempting to get target values, gives a better starting point to calculate from instead of starting from an unknown probability value of 1.0

* improve performance by performing expensive operations only when needed

when creating an array for the outcome in probability, it is only built if the cache key is not found

* Optimization for caching, deterministic calcs for branches, enabling/disabling cache

added more optimization for performance when performing calculations for branches using caching and targets, it will now also store the deterministic node values which gives a very slight performance increase, migrated to most efficient ways to store and retrieve cache values. The performance increase when using a large amount of qubits is huge, when using a very small amount of qubits the very slight over head of caching makes it about the same. Increasing test coverage for using targets with probabilities

* improved tests, checking of caching vs not caching

implemented more accurate tests that verify probability caching

* added more tests and variations for stabilizerstate

* Corrected deterministic probability calculations

* Fixed bug with cache in wrong place for some calculations

for some calculations the cached results were in the wrong place which could cause false results for certain test cases with 3+ qubits

* added more test cases

* Probabilities update for moved method call

* Added test for random test_probs_random_subsystem

* fixed single probabilities tests lost in previous merge

* Removed caching of deterministic values

overhead was too high compared to calculating

* Fixed failing test with test_probs_random_subsystem

Fixed failing test that would randomly occur, the stabilizer state was not being cached as well, which would sometimes produce a failure. After caching the object to restore when calculating starting at a node down the branch, this failure no longer occurs. Need to optimize and clean up the code a bit, but functioning

* Adding tests back that were not test_probs_random_subsystem, removed to test

* Created ProbabilityCache object for dealing with caching

* Finished adding ProbabilityCache object and commenting

* Added more documentation for stabilizer state

* Fixing style with tox -eblack

* commenting fixes in stabilizerstate

* Added release note

* Updated release notes

* added __future__ to probabilitiescache, removed unused import stabilizerstate

* run tox -eblack to correct probabilitiescache

* Corrected lint issues in multiple classes

fixed lint issues in test_probabilitiycache, test_stabilizerstate, stabilizerstate, and probabilitycache

* fixed more pylint lint issues

pylint -rn qiskit test tools
Corrected in probabilitycache, stabilizerstate, test_probabilitycach, test_stabilizerstate

* fixed more lint issues

fixed with:
tox -eblack

verified with:
black --check qiskit test tools examples setup.py
pylint -rn qiskit test tools
tox -elint

* added a bit more variance allowance for performance tests

renamed test_probabilitycache.py file to correct spelling
added a bit more performance_varability_percent percent time allowed from 0.001 to 0.005

* Corrected Lint issues, added test for probabilitycache

added test_probabilitycache test, fixed lint issues and type issues in stabilizerstate, test_stabilizerstate

* Fixed remaining import ordering issue

* readded ddt decorator for test_probabilitycache

* Changed to using process_time_ns from monotonic for performance measuring

* Added output when performance times fail to aid in debugging in test_stabilizerstate

* moved performance end time out of same method to calculate times

* changed method name in test_stabilizerstate

changed probability_percent_of_calculated_branches to _probability_percent_of_calculated_branches

* Added GC disabling between timed tests

* Attempting using perf_counter_ns instead of process_time_ns

* Updated commenting, moved 'X' outcome check

moved 'X' in outcome out of the for loop for StabilizerState._get_probabilities to help performance

* more comment updating

* Changed performance timer based on OS type

* Changing to time.thread_time_ns(), and raising performance varabilitiy by 0.005

using time.thread_time_ns() for benchmarking instead of time.perf_counter_ns() and time.process_time_ns() based on the OS in test_stabilizerstate. It seems that time is being counted that shouldn't be with the other methods that causes false reports of the work taking longer then it does. Raising the performance_varability_percent in test_stablilizerstate by 0.005 to 0.01 for more headroom for performance checking

* changing time.thread_time() vs time.thread_time_ns() to see if compatible with windows

also added one more target for test_4 to differentiate no caching against caching in test_stabilizerstate

* Changing performance timer based on OS for test_stablizerstate

perf_counter_ns for win32 and thread_time for all other OS

* fixed small issue in probabilitycache, improved probabilitycache test

fixed issue when checking the cache key that was not operating correctly, internally the key is always a str, but when a list[str] was passed in the check was not correct, so changed to check if an instance of a list, and will join the outcome val to a str for the internal cache key. Improved the probabilitycache to test having alternating keys of str and list[str] to test this internal key checking

* Added correct type hinting to probabilitycache

* removed typing hint module

removed the use of typing module for type hinting, looks like python wants to get away from using this module for type hinting, also fixed some commenting issues in stabilizerstate, test_probabilitycache, test_stabilizerstate, and probabilitycache files

* Fix test pylint failure of Value 'list' is unsubscriptable (unsubscriptable-object)

fixing tests failing with pylint when lint checking is performed. This worked locally and wasn't failing, but when it runs in the CI/CD process it fails, so adding from __future__ import annotations to avoid this failure for test_probabilitycache and test_stabilizerstate as new tests include type hinting

* Fix small comment error in test, Added use_caching to tests, simplified cache calls

Fixed a small error in my commenting for the test_stabilizerstate.py and forced use_caching parm in the call of probabilities_dict_from_bitstrings in the tests, even tho it is used as default, to make sure it is clear in the tests. Simplfied the adding values to Probabilitycache, and simplified the code in StabilizerState._get_probabilities() setting values in cache and retrieving the state when using cache, made cache_key in probabilitycache "private" and changed method to _cache_key

* Significant code refactoring in StabiizerState

Significant code refactoring in stabilizerstate. I was able to condense the use the cache to a smaller area in the _get_probabilities helper method so it is easier to work with and more clear. I removed methods I created that were single use helper methods that were able to be condensed down into simpler code such as _branches_to_measure, _retrieve_deterministic_probability, _branches_to_measure, _is_qubit_deterministic. Since these are 1 time use methods it didn't make sense to keep them around. The _get_probabilities helper method is much simpler and clearer, closer to the original with less tweaks and functioning with the same performance, and more clarity.  Most of the code changed at this point is adding tests, most of this commit was removing unnecessary code and refactoring

* Fixed stabilizerstate cache key outcome not found

there was a check to make sure a key could be found before that was removed in the refactoring, once and awhile you get a target that can't be found in the cache because of the algorithm of looking for a key by increasing the number of X from the left to the right. There are situations where the X would be in the middle such as 0X1 that won't be found. The algorithm in the probability cache could be changed to use BST which would very rarely help performance, the fix is to make sure a key can be found and then only use the cache if that is the case

* Moved all caching retrieval outside of _get_probabilities for performance

there is a slight overhead when recursively iterating through the _get_probabilities helper when having to check if caching should be retrieved. Realistically when you use caching it is just to get you a better starting point, so it only needs to be retrieved once. The cache inserting remains in the _get_probabilities helper to build up the cache, but the logic was significantly simplified in the _get_probabilities helper, and now only a 1 time cache retrieval is performed for each target via the probabilities_dict_from_bitstrings method

* Simplified Bitstring method for probability cache, removed unnecessary classes

Removed Probabilitycache class, test_probabilitiycache.py. Simplified test_stabilizerstate.py for new bitstring probability testing methods. changed method probabilities_dict_from_bitstrings to probabilities_dict_from_bitstring, and simplified by removing cache, and only allowing a single string value to be passed in

* Commenting update for _probabilities_bitstring_verify

updating the commenting for _probabilities_bitstring_verify, move to top of file

* black lint formatter ran

* Uncomment out line causing circular import

when running locally I get a circular import for two_qubit_decompose.py on line 154 "_specializations = two_qubit_decompose.Specialization", I need to comment out to test locally, but did not mean to commit this.

Simplified the stablizierstate self._get_probabilities method but removing an unnecessary check

* Remove type hints from test_stabilizerstate

* Updated test_stabilizerstate, removed helper test method, new release note

update the tests in test_stabilizerstate to no longer use a helper method for checking all the bitstring measurements in. Created a _get_probabilities_dict helper method in stabilizer state to be used for the probabilities_dict and probabilities_dict_with_bitstring methods. This was needed so that they could share the logic of the measurements, but also so that you could enforce in the probabilities_dict_with_bitstring method that outcome_bitstring must be provided. Previouslly this was optional and the user could avoid passing in a value, but this would essentially make it function the same as probabilities_dict. Now with the helper it enforces they must provide a bitstring

* Added large qubit test with h gates, reorder imports for test_stabilizerstate

added new test method test_probabilities_dict_large_num_qubits with large amount of qubit tests using an outcome bitstring target, num_qubits=[25, 50, 100, 200, 300, 400, 500, 600, 750], normally get any results for 750 qubits would require ~5.922387e+225 full bitstring calculations, but if you want one target bitstring you can perform ~750 measurements for a single probability for one result and get the value quickly

* moved methods out of loop that are unnecessary in test_stabilizerstate

moded target dict calculation out of loop that is not necessary to recalculate through each sample loop calculation

* broke out medium hgate test to own test method, issue with assertTrue type in test

testing a variety of hgates that also do the full probabilities_dict calculations up to 9 qubits called test_probabilities_dict_hgate_medium_num_qubits, also changed a assertTrue typo to assertDictEqual in the test_probabilities_dict_large_num_qubits method

* fixed commenting in stabilizerstate, removed unnecessary outcome_bitstring array access syntax

fixed some commenting for probabilities_dict_from_bitstring, and removed unnecessary syntax for accessing the array, was outcome_bitstring[i : i + 1] now outcome_bitstring[i] in _get_probabilities

* Condensed tests in test_stabilizerstate, added helper test class, more test coverage

added class StabilizerStateTestingTools in file test_stabilizerstate, these methods needed to be moved outside of the TestStabilizerState class because they are also needed in the TestStabilizerStateExpectationValue class within the file. It did not make sense to put these helper methods in the QiskitTestCase class, or the BaseQiskitTestCase class as these helpers are specific to helping test a new method in the stabilizerstate class.

I condensed redudnant code for checking the individual bitstrings into helper method _verify_individual_bitstrings which takes a target dict, uses each entry in the dict to attempt to get the single probability, and verify each, which drastically raises the test coverage of this new method probabilities_dict_from_bitstring.

added helper method StabilizerStateTestingTools._bitstring_product which builds a dict of the product of 0, 1 for a lenth passed in, used in several test cases for testing the zero probabilities.

In the test cases you will also notice cases where I update the target dict target.update(StabilizerStateTestingTools._bitstring_product( num_qubits, target)), which builds the rest of the bitstring values that will have a 0 probability, which is then passed into the _verify_individual_bitstrings method to verify they get the correct 0 value

I reduced the number of qubits for the test_probabilities_dict_hgate_large_num_qubits from tests of num_qubits=[25, 50, 100, 200, 300, 400, 500, 600, 750] to num_qubits=[25, 50, 100, 200] due to memory issues on windows, and the largest use case I have received so far is 100 qubits, so going above that by 2x

Overall test coverage should be dramatically increased now testing for 0 probabilities, checking every bitstring individually has a probability, and simplifying the code, removing a lot of repetative logic, moved to the helper methods

* Fixed isues in release notes

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1578906579

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1578907028

* Corrected Targetted to targeted in stablizerstate

* Updated commenting about target

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1579358105

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1579356207

* Moved helper function _get_probabilities_dict

moved the helper function _get_probabilities_dict to the helper function area next to _get_probabilities

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1579365690

* differentiated description for probabilities

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1579193692

* Added test to test_stabilizerstate

added a test that does not have all equal probabilities and is not a GHZ state

https://github.com/Qiskit/qiskit/pull/12147#discussion_r1579395521

* combined 2 test methods

combine the large and medium tests into one due to taking too long to run the tests and lowered the amount of qubits

* removed unnecessary test

removed redundant test in test_probabilities_dict_medium_num_qubits, as this was combined with the large test

* fixed lint issues, renamed test method

renamed test_probabilities_dict_medium_num_qubits in test_stabilizerstate to test_probabilities_dict_from_bitstring

* fixed type for targetting in stabilizerstate

typo for description for word targetting, changed to targeting

---------

Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
---
 qiskit/quantum_info/states/stabilizerstate.py | 161 +++++++++++---
 ...r_probabilities_dict-e53f524d115bbcfc.yaml |  13 ++
 .../states/test_stabilizerstate.py            | 203 ++++++++++++++++--
 3 files changed, 329 insertions(+), 48 deletions(-)
 create mode 100644 releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml

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/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/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))

From 70b36d061c5423dd0630c24df2a16be499a08c12 Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Thu, 9 May 2024 12:51:58 -0400
Subject: [PATCH 082/179] improve docstring of plot_circuit_layout (#12370)

* improve docstring of plot_circuit_layout

* "name" -> "index"
---
 qiskit/visualization/gate_map.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py
index d8ffb6e10388..c7cc0727a6c4 100644
--- a/qiskit/visualization/gate_map.py
+++ b/qiskit/visualization/gate_map.py
@@ -1122,7 +1122,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

From 24f1436fdb5d4061fde3a6fab6dd5139556f7e10 Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Thu, 9 May 2024 14:19:07 -0400
Subject: [PATCH 083/179] fix edge coloring bug in plot_coupling_map (#12369)

* fix edge coloring bug in plot circuit layout

* add release note
---
 qiskit/visualization/gate_map.py                             | 2 ++
 releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml | 5 +++++
 2 files changed, 7 insertions(+)
 create mode 100644 releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml

diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py
index c7cc0727a6c4..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
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.

From 8c8c78a345caf35c85e30387b200565fd7f970de Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Thu, 9 May 2024 15:11:30 -0400
Subject: [PATCH 084/179] Reorganize API index page into sections (#12333)

* Reorganize API index page into sections

* Sort alphabetically within each section

* Clarify ordering expectation
---
 docs/apidoc/index.rst | 85 +++++++++++++++++++++++++++++++++----------
 1 file changed, 66 insertions(+), 19 deletions(-)

diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst
index 3a6c1b04cfdf..30bb20998d6b 100644
--- a/docs/apidoc/index.rst
+++ b/docs/apidoc/index.rst
@@ -1,42 +1,89 @@
 .. 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
    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

From 4a6c57044f35f4fab62b3f10764c6463a2b75f03 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 9 May 2024 15:31:06 -0400
Subject: [PATCH 085/179] Fix runtime scaling of StarPreRouting pass (#12376)

This commit fixes a runtime performance scaling issue with the new
StarPreRouting pass. If there are any stars identified by the pass when
the pass goes to pre-route those star connectivity blocks it specifies a
custom lexicographical topological sort key to ensure the stars are
kept together in the sort. However the mechanism by which this sort key
was generated scaled quadratically with the number of DAG nodes in the
identified stars. This ended up being a large runtime performance
bottleneck. This commit fixes this issue by pre-computing the sort key
for all nodes in the stars and putting that in a dictionary so that when
we call rustworkx to perform the topological sort the sort key callback
does not become the bottleneck for the entire pass.
---
 qiskit/transpiler/passes/routing/star_prerouting.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

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)

From 48709afe0489febf965c237696e4f3376936287f Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Thu, 9 May 2024 15:59:11 -0400
Subject: [PATCH 086/179] Removing unnecessary-dict-index-lookup lint rule and
 updates (#12373)

---
 pyproject.toml              | 1 -
 qiskit/transpiler/target.py | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 9c7094827c8a..303192e75cf9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -226,7 +226,6 @@ disable = [
     "no-value-for-parameter",
     "not-context-manager",
     "unexpected-keyword-arg",
-    "unnecessary-dict-index-lookup",
     "unnecessary-dunder-call",
     "unnecessary-lambda-assignment",
     "unspecified-encoding",
diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py
index 8d609ce3b8a3..466d93fc89e8 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -814,7 +814,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

From 4ede4701d1b245f6ce35aa184a938e3be41711b2 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Thu, 9 May 2024 16:02:40 -0400
Subject: [PATCH 087/179] Removing consider-iterating-dictionary lint rule and
 updates (#12366)

---
 pyproject.toml                                                | 1 -
 qiskit/circuit/library/n_local/pauli_two_design.py            | 2 +-
 qiskit/pulse/parser.py                                        | 4 ++--
 .../synthesis/discrete_basis/generate_basis_approximations.py | 2 +-
 qiskit/synthesis/discrete_basis/solovay_kitaev.py             | 2 +-
 qiskit/transpiler/passes/synthesis/plugin.py                  | 4 ++--
 qiskit/visualization/circuit/matplotlib.py                    | 2 +-
 qiskit/visualization/circuit/qcstyle.py                       | 4 ++--
 test/python/transpiler/test_high_level_synthesis.py           | 4 ++--
 9 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 303192e75cf9..45725f51bb48 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -218,7 +218,6 @@ disable = [
 #  with the rationale
     "arguments-renamed",
     "broad-exception-raised",
-    "consider-iterating-dictionary",
     "consider-using-dict-items",
     "consider-using-enumerate",
     "consider-using-f-string",
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/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/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/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/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/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 []

From b80885d1d617bd96ab5314db1b50d959258c2f93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?=
 <57907331+ElePT@users.noreply.github.com>
Date: Thu, 9 May 2024 22:15:36 +0200
Subject: [PATCH 088/179] Fix missing layout in `Commuting2qGateRouter`
 (#12137)

* Add layout to property set, add test

* Apply comments from code review

* Add virtual permutation instead of final layout. Test
---
 .../commuting_2q_gate_router.py               |  9 +++-
 ...x-swap-router-layout-f28cf0a2de7976a8.yaml |  7 +++
 .../transpiler/test_swap_strategy_router.py   | 44 ++++++++++++++++++-
 3 files changed, 57 insertions(+), 3 deletions(-)
 create mode 100644 releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml

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/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/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."""

From 235e581b1f76f29add0399989ed47f47a4e98bb8 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Fri, 10 May 2024 06:19:31 -0400
Subject: [PATCH 089/179] Removing consider-using-dict-items from lint
 exclusions and updates (#12288)

* removing consider-using-dict-items from lint exclusions and updates

* switching items to values

* latex initialization updates for readability

* quick update for using items after removing lint rule

* Github to GitHub in SECURITY.md
---
 SECURITY.md                                   |  4 ++--
 pyproject.toml                                |  1 -
 qiskit/dagcircuit/collect_blocks.py           |  4 ++--
 .../basic_provider/basic_simulator.py         |  4 ++--
 .../providers/models/backendconfiguration.py  |  4 ++--
 qiskit/providers/options.py                   | 16 ++++++-------
 qiskit/pulse/library/symbolic_pulses.py       |  4 ++--
 qiskit/result/models.py                       |  8 +++----
 qiskit/result/result.py                       |  8 +++----
 .../optimization/collect_multiqubit_blocks.py |  4 ++--
 qiskit/transpiler/target.py                   |  6 +++--
 qiskit/visualization/circuit/latex.py         | 23 +++++++++++--------
 qiskit/visualization/circuit/text.py          |  3 +--
 .../primitives/test_backend_sampler_v2.py     |  4 ++--
 .../primitives/test_statevector_sampler.py    |  4 ++--
 15 files changed, 51 insertions(+), 46 deletions(-)

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/pyproject.toml b/pyproject.toml
index 45725f51bb48..9a12ac25fd73 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -218,7 +218,6 @@ disable = [
 #  with the rationale
     "arguments-renamed",
     "broad-exception-raised",
-    "consider-using-dict-items",
     "consider-using-enumerate",
     "consider-using-f-string",
     "no-member",
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/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/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..e96ffc578429 100644
--- a/qiskit/providers/options.py
+++ b/qiskit/providers/options.py
@@ -233,24 +233,24 @@ def set_validator(self, field, validator_value):
 
     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/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/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/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/target.py b/qiskit/transpiler/target.py
index 466d93fc89e8..5af9e8686583 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -940,7 +940,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):
@@ -979,7 +981,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/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/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/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."""

From 5fd7d01c447325b09bd236856c1c883f413dc08d Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Fri, 10 May 2024 06:30:38 -0400
Subject: [PATCH 090/179] Moving the unsupported-assignment-operation lint rule
 (#12304)

* Moving the unsupported-assignment-operation lint rule

* moving lint exclusion to inline comment
---
 pyproject.toml              | 1 -
 qiskit/providers/options.py | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 9a12ac25fd73..1fde1d9991e0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -227,7 +227,6 @@ disable = [
     "unnecessary-dunder-call",
     "unnecessary-lambda-assignment",
     "unspecified-encoding",
-    "unsupported-assignment-operation",
 ]
 
 enable = [
diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py
index e96ffc578429..659af518a2b7 100644
--- a/qiskit/providers/options.py
+++ b/qiskit/providers/options.py
@@ -229,7 +229,7 @@ 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"""

From fe275a0f15132073c6b5afb0c4be8bcc351479cb Mon Sep 17 00:00:00 2001
From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Date: Fri, 10 May 2024 15:34:16 +0400
Subject: [PATCH 091/179] Fix {Pauli,SparsePauliOp}.apply_layout to raise an
 error with negative or duplicate indices (#12385)

* fix apply_layout to raise an error with negative or duplicate indices

* reno

* Fix Sphinx syntax

* Fix my own Sphinx lookup problem

---------

Co-authored-by: Jake Lishman 
---
 qiskit/quantum_info/operators/symplectic/pauli.py    |  7 +++++--
 .../operators/symplectic/sparse_pauli_op.py          |  8 +++++---
 ...-duplicate-negative-indices-cf5517921fe52706.yaml |  6 ++++++
 .../quantum_info/operators/symplectic/test_pauli.py  | 12 ++++++++++++
 .../operators/symplectic/test_sparse_pauli_op.py     | 12 ++++++++++++
 5 files changed, 40 insertions(+), 5 deletions(-)
 create mode 100644 releasenotes/notes/fix-apply-layout-duplicate-negative-indices-cf5517921fe52706.yaml

diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index 1ccecc04a6c0..8187c1ee41e6 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli.py
@@ -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..cf51579bef8f 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,13 @@ 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.")
         new_op = type(self)("I" * n_qubits)
         return new_op.compose(self, qargs=layout)
 
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/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py
index 875dd9237810..89324e8212e1 100644
--- a/test/python/quantum_info/operators/symplectic/test_pauli.py
+++ b/test/python/quantum_info/operators/symplectic/test_pauli.py
@@ -606,6 +606,18 @@ 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)
+
 
 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..1149ef1f3466 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
@@ -1179,6 +1179,18 @@ 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)
+
 
 if __name__ == "__main__":
     unittest.main()

From f20dad0d03281bf3e628a7c32c117f41140365d4 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Fri, 10 May 2024 14:43:25 -0400
Subject: [PATCH 092/179] Removing broad-exception-raised lint rule and updates
 (#12356)

* Removing broad-exception-raised lint rule and updates

* updating assertion types based on review

* updating tests based on review
---
 pyproject.toml                                     | 1 -
 qiskit/quantum_info/quaternion.py                  | 2 +-
 qiskit/synthesis/clifford/clifford_decompose_bm.py | 2 +-
 qiskit/visualization/bloch.py                      | 4 ++--
 qiskit/visualization/transition_visualization.py   | 4 ++--
 test/benchmarks/utils.py                           | 2 +-
 test/python/quantum_info/test_quaternions.py       | 6 ++++--
 test/python/transpiler/test_preset_passmanagers.py | 2 +-
 test/python/transpiler/test_sabre_swap.py          | 2 +-
 test/python/transpiler/test_stochastic_swap.py     | 2 +-
 10 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 1fde1d9991e0..35a14a5524ba 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -217,7 +217,6 @@ 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-using-enumerate",
     "consider-using-f-string",
     "no-member",
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/synthesis/clifford/clifford_decompose_bm.py b/qiskit/synthesis/clifford/clifford_decompose_bm.py
index 50ffcb743162..4800890a90d3 100644
--- a/qiskit/synthesis/clifford/clifford_decompose_bm.py
+++ b/qiskit/synthesis/clifford/clifford_decompose_bm.py
@@ -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/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/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/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/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/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 a9fec85be665..5315c4b8e018 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 df5948ed715b..5b924e590a54 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:

From 8985b24c6b8f1886a681731cd8bf74b8202bfff7 Mon Sep 17 00:00:00 2001
From: Kevin Hartman 
Date: Tue, 14 May 2024 05:41:49 -0400
Subject: [PATCH 093/179] [DAGCircuit Oxidation] Port `DAGNode` to Rust
 (#12380)

* Checkpoint before rebase.

* [Tests passing] Port DAGNode classes to Rust.

Also has beginnings of DAGCircuit, but this is not
complete or wired up at all.

* Revert DAGCircuit stuff.

* Fix lint.

* Address review comments.

* Python lint + format

* Fix format

* Remove unnecessary state methods from CircuitInstruction pickling.
---
 crates/circuit/src/circuit_instruction.rs |  18 +-
 crates/circuit/src/dag_node.rs            | 283 ++++++++++++++++++++++
 crates/circuit/src/lib.rs                 |   5 +
 qiskit/dagcircuit/dagnode.py              | 223 +++++------------
 4 files changed, 355 insertions(+), 174 deletions(-)
 create mode 100644 crates/circuit/src/dag_node.rs

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/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)

From 58a383d02a060d85da13b00fd9483da1e4f8139f Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Tue, 14 May 2024 13:50:08 +0100
Subject: [PATCH 094/179] Fix `QuantumCircuit.compose` with `Index` exprs
 (#12396)

This was an oversight in d6c74c265 (gh-12310), where an `ExprVisitor`
was missed in the testing.
---
 qiskit/circuit/_classical_resource_map.py |  3 +++
 test/python/circuit/test_compose.py       | 12 +++++++++++-
 2 files changed, 14 insertions(+), 1 deletion(-)

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/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):

From fe695946995ccd45059d678ffbc7852bcb489775 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Tue, 14 May 2024 16:26:27 +0100
Subject: [PATCH 095/179] Handle huge integers in OpenQASM 2 expression
 evaluator (#12140)

* Handle huge integers in OpenQASM 2 expression evaluator

This modifies the expression evaluator to directly parse the backing
string data of an integer token in a floating-point context, which lets
us handle numbers that would otherwise overflow a `usize`.  It's
possible for this to be totally valid, if, for example, the integer is a
multiple of some very large power of two that doesn't overflow a
double-precision float.

We already needed to immediately cast the integer to a float, so this is
just a more accurate way of doing the evaluation, and doesn't affect
when we use integers in other contexts.

* Clarify int/float split
---
 crates/qasm2/src/expr.rs                           | 14 ++++++++++++--
 crates/qasm2/src/lex.rs                            |  4 ++--
 .../notes/qasm2-bigint-8eff42acb67903e6.yaml       |  9 +++++++++
 test/python/qasm2/test_expression.py               | 12 ++++++++++++
 4 files changed, 35 insertions(+), 4 deletions(-)
 create mode 100644 releasenotes/notes/qasm2-bigint-8eff42acb67903e6.yaml

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/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/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):

From cea93a051671b4c56945e916a70d22c2f5d488d8 Mon Sep 17 00:00:00 2001
From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
Date: Tue, 14 May 2024 20:09:01 +0400
Subject: [PATCH 096/179] Fix a corner case of `SparsePauliOp.apply_layout`
 (#12375)

* fix a corner case of `SparsePauliOp.apply_layout`

* Add zero-qubit tests of Pauli.apply_layout

* use combine and apply isort

* Update releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml

---------

Co-authored-by: Matthew Treinish 
---
 .../operators/symplectic/sparse_pauli_op.py   |  2 +
 ...op-apply-layout-zero-43b9e70f0d1536a6.yaml | 10 +++++
 .../operators/symplectic/test_pauli.py        | 40 +++++++++++--------
 .../symplectic/test_sparse_pauli_op.py        | 31 ++++++++++----
 4 files changed, 58 insertions(+), 25 deletions(-)
 create mode 100644 releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml

diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index cf51579bef8f..440a0319c33a 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -1165,6 +1165,8 @@ def apply_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/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/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py
index 89324e8212e1..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}
 
@@ -618,6 +617,13 @@ def test_apply_layout_duplicate_indices(self):
         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 1149ef1f3466..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):
@@ -1191,6 +1190,22 @@ def test_apply_layout_duplicate_indices(self):
         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()

From 07c8f20481813493eae80164731794fccdc1de3d Mon Sep 17 00:00:00 2001
From: Jim Garrison 
Date: Tue, 14 May 2024 16:47:29 -0400
Subject: [PATCH 097/179] Fix two cross-references in `BasisTranslator`
 docstring (#12398)

---
 qiskit/transpiler/passes/basis/basis_translator.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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.
     """
 

From c6c45a1de9140d3051f34a90a8486e689f0a8749 Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Tue, 14 May 2024 23:37:41 -0400
Subject: [PATCH 098/179] export SamplerPubResult in qiskit.primitives module
 (#12406)

---
 qiskit/primitives/__init__.py | 2 ++
 1 file changed, 2 insertions(+)

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,

From d4522251d33fe34419a725c6546f3268df76e907 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 15 May 2024 09:40:38 -0400
Subject: [PATCH 099/179] Bump pulp from 0.18.10 to 0.18.12 (#12409)

Bumps [pulp](https://github.com/sarah-ek/pulp) from 0.18.10 to 0.18.12.
- [Commits](https://github.com/sarah-ek/pulp/commits)

---
updated-dependencies:
- dependency-name: pulp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock                   | 4 ++--
 crates/accelerate/Cargo.toml | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b6915167f9c8..d812f8fc1c58 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -989,9 +989,9 @@ dependencies = [
 
 [[package]]
 name = "pulp"
-version = "0.18.10"
+version = "0.18.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e14989307e408d9f4245d4fda09a7b144a08114ba124e26cab60ab83dc98db10"
+checksum = "140dfe6dada20716bd5f7284406747c73061a56a0a5d4ad5aee7957c5f71606c"
 dependencies = [
  "bytemuck",
  "libm",
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index a43fdc6ff506..4f2c80ebff2b 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -53,5 +53,5 @@ version = "0.1.0"
 features = ["ndarray"]
 
 [dependencies.pulp]
-version = "0.18.10"
+version = "0.18.12"
 features = ["macro"]

From b12e9ec3cff020983e3dde9b16f5ccc4fd0f4963 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Wed, 15 May 2024 17:56:11 +0100
Subject: [PATCH 100/179] Fix unnecessary serialisation of `PassManager` in
 serial contexts (#12410)

* Fix unnecessary serialisation of `PassManager` in serial contexts

This exposes the interal decision in `parallel_map` of whether to
actually run in serial or not.  If not, there's no need for
`PassManager` to side-car its `dill` serialisation onto the side of the
IPC (we use `dill` because we need to pickle lambdas), which can be an
unfortunately huge cost for certain IBM pulse-enabled backends.

* Remove new function from public API

This makes the patch series safe for backport to 1.1.
---
 qiskit/passmanager/passmanager.py             | 22 +++++------
 qiskit/utils/__init__.py                      |  5 ++-
 qiskit/utils/parallel.py                      | 39 ++++++++++++-------
 .../parallel-check-8186a8f074774a1f.yaml      |  5 +++
 4 files changed, 43 insertions(+), 28 deletions(-)
 create mode 100644 releasenotes/notes/parallel-check-8186a8f074774a1f.yaml

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/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/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.

From 96607f6b2068594f216928b954db0f516e1466ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iy=C3=A1n?= 
Date: Thu, 16 May 2024 08:52:54 +0200
Subject: [PATCH 101/179] Avoid lossing precision when scaling frequencies
 (#12392)

* Avoid lossing precision when scaling frequencies

Classes in pulse_instruction.py scale frequency values to GHz by
multipliying `ParameterExpression` with float 1e9. This can lead
to numerical errors on some systems using symengine. Instead, this
scaling can be done multiplying by integer 10**9.

See: https://github.com/Qiskit/qiskit/issues/12359#issuecomment-2104426621

* Add release note

---------

Co-authored-by: Jake Lishman 
---
 qiskit/qobj/converters/pulse_instruction.py          | 12 ++++++------
 .../fix-symbolic-unit-scaling-c3eb4d9be674dfd6.yaml  |  8 ++++++++
 2 files changed, 14 insertions(+), 6 deletions(-)
 create mode 100644 releasenotes/notes/fix-symbolic-unit-scaling-c3eb4d9be674dfd6.yaml

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/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 `__.

From 72ad545a87e1cbd79afe4bbc64f5b835a8e92f7e Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 16 May 2024 14:40:46 +0100
Subject: [PATCH 102/179] Write complete manual `QuantumCircuit` documentation
 (#12403)

* Write complete manual `QuantumCircuit` documentation

This writes huge tracts of new `QuantumCircuit` API documentation,
linking together alike methods and writing explanatory text for how all
the components fit together.  There's likely an awful lot more that
could go into this too, but this hopefully should impose a lot more
order on the huge `QuantumCircuit` documentation page, and provide a lot
more explanation for how the class works holistically.

In particular, the section on the control-flow builder interface could
do with a lot more exposition and examples right now.

* Reword from review

Co-authored-by: Matthew Treinish 

* Add missing text around `Var` methods

* Add example to `find_bit`

* Comment on metadata through serialization

* Add see-also sections to undesirable methods

* Re-add internal utilities to documentation

* Add examples to `depth`

---------

Co-authored-by: Matthew Treinish 
---
 docs/apidoc/index.rst                         |    1 +
 docs/apidoc/qiskit.circuit.QuantumCircuit.rst |   17 +
 qiskit/circuit/__init__.py                    |  163 +--
 qiskit/circuit/quantumcircuit.py              | 1216 +++++++++++++++--
 4 files changed, 1098 insertions(+), 299 deletions(-)
 create mode 100644 docs/apidoc/qiskit.circuit.QuantumCircuit.rst

diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst
index 30bb20998d6b..8581d56ace77 100644
--- a/docs/apidoc/index.rst
+++ b/docs/apidoc/index.rst
@@ -13,6 +13,7 @@ Circuit construction:
    :maxdepth: 1
 
    circuit
+   qiskit.circuit.QuantumCircuit
    circuit_classical
    classicalfunction
    circuit_library
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/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py
index ff36550967d8..3982fa873349 100644
--- a/qiskit/circuit/__init__.py
+++ b/qiskit/circuit/__init__.py
@@ -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:
 
@@ -920,122 +903,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/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(

From 554e661ee62ba6db1cfcbef0fabd98a1659a1641 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 16 May 2024 18:31:55 +0100
Subject: [PATCH 103/179] Add release notes for manual `Var` and `Store`
 (#12421)

* Add release notes for manual `Var` and `Store`

This adds the release notes and updates some small portions of
documentation that were previously missed surrounding the new manual
`Var` storage locations.

This includes documenting all new keyword arguments to methods, upgrade
instructions for providers, and adding the `Var.new` method to the
documentation, which was previously erroneously omitted.

* Fix Sphinx typo

* Fix another Sphinx typo

* Move QPY version bump to upgrade

* Unify base release note

* Reword providers upgrade note

Co-authored-by: Matthew Treinish 

---------

Co-authored-by: Matthew Treinish 
---
 qiskit/circuit/classical/expr/__init__.py     |   2 +-
 qiskit/providers/__init__.py                  |  39 +++++-
 .../1.1/classical-store-e64ee1286219a862.yaml |  13 ++
 .../notes/storage-var-a00a33fcf9a71f3f.yaml   | 122 ++++++++++++++++++
 4 files changed, 174 insertions(+), 2 deletions(-)
 create mode 100644 releasenotes/notes/storage-var-a00a33fcf9a71f3f.yaml

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/providers/__init__.py b/qiskit/providers/__init__.py
index b0ebc942523c..6736d67a214b 100644
--- a/qiskit/providers/__init__.py
+++ b/qiskit/providers/__init__.py
@@ -452,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.
 
+.. _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
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/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.

From 8b569959d046614e338e100cb8033e5918e778eb Mon Sep 17 00:00:00 2001
From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com>
Date: Fri, 17 May 2024 02:58:50 +0200
Subject: [PATCH 104/179] Fix `qiskit.circuit` method header and broken
 cross-reference (#12394)

* Fix qiskit.circuit method header

* use object

* fix lint

* Correct method to be defined on `object`

---------

Co-authored-by: Jake Lishman 
---
 qiskit/circuit/__init__.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py
index 3982fa873349..43087760153e 100644
--- a/qiskit/circuit/__init__.py
+++ b/qiskit/circuit/__init__.py
@@ -816,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`.

From 8571afe3775fdd2f72354142d14bfc2ac0292da6 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Fri, 17 May 2024 08:59:31 -0400
Subject: [PATCH 105/179] Add merge queue to required tests on github actions
 (#12428)

This commit adds the missing config to the github actions workflow for
running required tests (currently only arm64 macOS test jobs) to the
merge queue. This is necessary to make the job required in the branch
protection rules, because the jobs will need to pass as part of the
merge queue too.
---
 .github/workflows/tests.yml | 1 +
 1 file changed, 1 insertion(+)

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 }}

From 4e3de44bcbde61fae33848a94be2622f5f5bd959 Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Fri, 17 May 2024 12:55:48 -0400
Subject: [PATCH 106/179] Add missing paranthesis to pauli_feature_map.py
 (#12434)

---
 qiskit/circuit/library/data_preparation/pauli_feature_map.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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()

From 581f24784d5267261c06ead8ec9adf303e291b5a Mon Sep 17 00:00:00 2001
From: "Kevin J. Sung" 
Date: Fri, 17 May 2024 16:49:16 -0400
Subject: [PATCH 107/179] add insert_barrier argument to UnitaryOverlap
 (#12321)

* add insert_barrier argument to UnitaryOverlap

* set fold=-1 in circuit drawing
---
 qiskit/circuit/library/overlap.py           | 10 +++++++++-
 test/python/circuit/library/test_overlap.py | 15 +++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

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/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()

From 16acb80a03fde1d3e54b75f8ea18ec60661498bf Mon Sep 17 00:00:00 2001
From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com>
Date: Mon, 20 May 2024 16:04:42 +0200
Subject: [PATCH 108/179] Remove the duplicated docs for a BackendV1
 classmethod (#12443)

---
 qiskit/providers/backend.py | 6 ------
 1 file changed, 6 deletions(-)

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()

From 8e3218bc0798b0612edf446db130e95ac9404968 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Mon, 20 May 2024 19:10:25 +0100
Subject: [PATCH 109/179] Remove reference to `transpile` from Pulse docs
 (#12448)

* Remove reference to `transpile` from Pulse docs

This reference to `transpile` was mistakenly inserted as part of 1a027ac
(gh-11565); `execute` used to ignore the `transpile` step for
`Schedule`/`ScheduleBlock` and submit directly to `backend.run`.

I am not clear if there are actually any backends that *accept* pulse
jobs anymore, but if there are, this is what the text should have been
translated to.

* Update qiskit/pulse/builder.py

Co-authored-by: Will Shanks 

---------

Co-authored-by: Will Shanks 
---
 qiskit/pulse/builder.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

From 9d0ae64f6dba20872a5f62a7f2d2cb15983582ef Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 22 May 2024 06:51:40 -0400
Subject: [PATCH 110/179] Bump itertools from 0.12.1 to 0.13.0 (#12427)

Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.12.1 to 0.13.0.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.12.1...v0.13.0)

---
updated-dependencies:
- dependency-name: itertools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock                   | 6 +++---
 crates/accelerate/Cargo.toml | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index d812f8fc1c58..27b5cddb3133 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -605,9 +605,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",
 ]
@@ -1102,7 +1102,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/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index 4f2c80ebff2b..0bbe5911a865 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -21,7 +21,7 @@ num-complex = "0.4"
 num-bigint = "0.4"
 rustworkx-core = "0.14"
 faer = "0.18.2"
-itertools = "0.12.1"
+itertools = "0.13.0"
 qiskit-circuit.workspace = true
 
 [dependencies.smallvec]

From fc17d60447fb754ffd87fab04fe5c613d5418968 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Wed, 22 May 2024 11:12:23 -0400
Subject: [PATCH 111/179] Update CITATION.bib file to point to Qiskit
 whitepaper (#12415)

* Update CITATION.bib file to point to Qiskit whitepaper

This commit updates the official citation for the Qiskit project to
point to the overview paper "Quantum computing with Qiskit" which was
recently published on arxiv:

https://arxiv.org/abs/2405.08810

* Apply suggestions from code review

Co-authored-by: Luciano Bello 
Co-authored-by: Jake Lishman 

---------

Co-authored-by: Luciano Bello 
Co-authored-by: Jake Lishman 
---
 CITATION.bib | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

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}
 }

From f08c579ce13a7617a23184f88d28e4a7f98e4412 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Wed, 22 May 2024 12:31:43 -0400
Subject: [PATCH 112/179] Expose internal rust interface to DenseLayout
 (#12104)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Expose internal rust interface to DenseLayout

This commit makes a small change to the rust code for DenseLayout that
enables calling it more easily from rust. The primary obstacle was the
pyfunction used PyReadonlyArray2 inputs which precludes calling it
with rust constructed Array2Views. This adds a new inner public
function which takes the array view directly and then the pyfunction's
only job is to convert the inputs and outputs to Python. The python side
of the function is still building a sparse matrix and then runs reverse
Cuthill–McKee to get a permutation of the densest subgraph so any rust
consumers will want to keep that in mind (and maybe use sprs to do the
same).

At the same time it corrects an oversight in the original implementation
where the returned numpy arrays of the densest subgraph are copied
instead of being returned as references. This should slightly improve
performance by eliminating 3 array copies that weren't needed.

* Remove PyResult

---------

Co-authored-by: Henry Zou <87874865+henryzou50@users.noreply.github.com>
---
 crates/accelerate/src/dense_layout.rs | 37 +++++++++++++++++++++------
 1 file changed, 29 insertions(+), 8 deletions(-)

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]

From 531f91c24bb4f5bcca44bddceb3e2ef336a72044 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Wed, 22 May 2024 15:25:32 -0400
Subject: [PATCH 113/179] Add `DenseLayout` trial to `SabreLayout` (#12453)

Building on the work done in #10829, #10721, and #12104 this commit adds
a new trial to all runs of `SabreLayout` that runs the dense layout
pass. In general the sabre layout algorithm starts from a random layout
and then runs a routing algorithm to permute that layout virtually where
swaps would be inserted to select a layout that would result in fewer
swaps. As was discussed in #10721 and #10829 that random starting point
is often not ideal especially for larger targets where the distance
between qubits can be quite far. Especially when the circuit qubit count
is low relative to the target's qubit count this can result it poor
layouts as the distance between the qubits is too large. In qiskit we
have an existing pass, `DenseLayout`, which tries to find the most
densely connected n qubit subgraph of a connectivity graph. This
algorithm necessarily will select a starting layout where the qubits are
near each other and for those large backends where the random starting
layout doesn't work well this can improve the output quality.

As the overhead of `DenseLayout` is quite low and the core algorithm is
written in rust already this commit adds a default trial that uses
DenseLayout as a starting point on top of the random trials (and any
input starting points). For example if the user specifies to run
SabreLayout with 20 layout trials this will run 20 random trials and
one trial with `DenseLayout` as the starting point. This is all done
directly in the sabre layout rust code for efficiency. The other
difference between the standalone `DenseLayout` pass is that in the
standalone pass a sparse matrix is built and a reverse Cuthill-McKee
permutation is run on the densest subgraph qubits to pick the final
layout. This permutation is skipped because in the case of Sabre's
use of dense layout we're relying on the sabre algorithm to perform
the permutation.

Depends on: #12104
---
 crates/accelerate/src/sabre/layout.rs | 32 +++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

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()
+}

From 44c0ce3686835f86d30a451babbb7cb34b1f14a5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 May 2024 12:53:57 +0000
Subject: [PATCH 114/179] Bump pulp from 0.18.12 to 0.18.21 (#12457)

Bumps [pulp](https://github.com/sarah-ek/pulp) from 0.18.12 to 0.18.21.
- [Commits](https://github.com/sarah-ek/pulp/commits)

---
updated-dependencies:
- dependency-name: pulp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock                   | 4 ++--
 crates/accelerate/Cargo.toml | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 27b5cddb3133..e8c855d0b0e2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -989,9 +989,9 @@ dependencies = [
 
 [[package]]
 name = "pulp"
-version = "0.18.12"
+version = "0.18.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "140dfe6dada20716bd5f7284406747c73061a56a0a5d4ad5aee7957c5f71606c"
+checksum = "0ec8d02258294f59e4e223b41ad7e81c874aa6b15bc4ced9ba3965826da0eed5"
 dependencies = [
  "bytemuck",
  "libm",
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index 0bbe5911a865..de6f41dbfde3 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -53,5 +53,5 @@ version = "0.1.0"
 features = ["ndarray"]
 
 [dependencies.pulp]
-version = "0.18.12"
+version = "0.18.21"
 features = ["macro"]

From 529da36fa6b8d591de2cf2fac837dcd8ed9d16a7 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Tue, 28 May 2024 09:25:07 -0400
Subject: [PATCH 115/179] Pin nightly rust version to known working build
 (#12468)

In the past couple of days we've seen the miri tests fail in CI while
attempting to build crossbeam-epoch from source. We need to do this to
ensure that crossbeam-epoch is miri safe so that we can run our own
tests of Qiskit's unsafe code in ci. This failure was likely an issue in
the recent nightly builds so this commit pins the nightly rust version
to one from last week when everything was known to be working. We can
remove this specific pin when we know that upstream rust has fixed the
issue.
---
 .github/workflows/miri.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

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

From 0f0a6347d04620d8ff874458f6e5a722204e9a63 Mon Sep 17 00:00:00 2001
From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Date: Tue, 28 May 2024 20:59:38 +0300
Subject: [PATCH 116/179] Fix a bug in isometry.rs (#12469)

* remove assertion

* extend the test

* add release notes

* fix release notes

* Update releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml

Co-authored-by: Matthew Treinish 

---------

Co-authored-by: Matthew Treinish 
---
 crates/accelerate/src/isometry.rs                          | 1 -
 releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml | 6 ++++++
 test/python/circuit/test_controlled_gate.py                | 5 ++---
 3 files changed, 8 insertions(+), 4 deletions(-)
 create mode 100644 releasenotes/notes/fix-isometry-rust-adf0eed09c6611f1.yaml

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/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/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)

From 473c3c2e58f855332532c2c47a11db5695c0180f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 28 May 2024 18:12:33 +0000
Subject: [PATCH 117/179] Bump faer from 0.18.2 to 0.19.0 (#12466)

* Bump faer from 0.18.2 to 0.19.0

Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.18.2 to 0.19.0.
- [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sarah-ek/faer-rs/commits)

---
updated-dependencies:
- dependency-name: faer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 

* Bump faer-ext to 0.2.0 too

The faer and faer-ext versions need to be kept in sync. This commit
bumps the faer-ext version as part of the dependabot automated bump for
faer to do this.

---------

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthew Treinish 
---
 Cargo.lock                   | 120 ++++++++++++++++++++++++++++-------
 crates/accelerate/Cargo.toml |   4 +-
 2 files changed, 98 insertions(+), 26 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e8c855d0b0e2..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",
@@ -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"
@@ -740,6 +811,7 @@ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
 dependencies = [
  "bytemuck",
  "num-traits",
+ "rand",
 ]
 
 [[package]]
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index de6f41dbfde3..63be9ad90b44 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -20,7 +20,7 @@ num-traits = "0.2"
 num-complex = "0.4"
 num-bigint = "0.4"
 rustworkx-core = "0.14"
-faer = "0.18.2"
+faer = "0.19.0"
 itertools = "0.13.0"
 qiskit-circuit.workspace = true
 
@@ -49,7 +49,7 @@ workspace = true
 features = ["rayon"]
 
 [dependencies.faer-ext]
-version = "0.1.0"
+version = "0.2.0"
 features = ["ndarray"]
 
 [dependencies.pulp]

From df379876ba10d6f490a96723b6dbbf723ec45d7a Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Wed, 29 May 2024 13:27:54 -0400
Subject: [PATCH 118/179] Removing _name attribute from Parameter (#12191)

* removing _name attribute from Parameter

* lint fix for parameter update

* fix the __getstate__ structure
---
 qiskit/circuit/parameter.py | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

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}

From 61323662a7b7349aa1d47fc0d39f3bf04fdbc931 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?=
 <57907331+ElePT@users.noreply.github.com>
Date: Fri, 31 May 2024 14:43:10 +0200
Subject: [PATCH 119/179] Update `transpile()` and
 `generate_preset_pass_manager` to convert loose input of constraints to a
 `Target` with `Target.from_configuration()` (#12185)

* Update transpile() to convert BackendV1 inputs to BackendV2 with BackendV2Converter

* Rework use of instruction durations, move logic from transpile function to individual passes.

* Convert loose constraints to target inside transpile. Cover edge cases where num_qubits and/or basis_gates is None.

* Fix basis gates oversights, characterize errors in test_transpiler.py and comment out failing cases (temporary).

* Fix pulse oversights

* Add backend instructions to name mapping by default

* Fix edge case 3: Add control flow to default basis gates

* Add path for regular cases and path for edge cases that skips target construction.

* Complete edge case handling, fix tests.

* Update reno

* Apply suggestion from Ray's code review

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* * Migrate full target-building capabilities from transpile to generate_preset_pass_manager.

* Apply Matt's suggestions for custom mapping and control flow op names.

* Extend docstring, fix typos.

* Fix lint, update reno

* Create new reno for 1.2 instead of modifying the 1.1 one.

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
---
 qiskit/compiler/transpiler.py                 | 119 ++------
 .../preset_passmanagers/__init__.py           | 280 +++++++++++++++++-
 qiskit/transpiler/target.py                   |   7 +-
 ...n-generate-preset-pm-5215e00d22d0205c.yaml |  14 +
 test/python/compiler/test_transpiler.py       |   8 +-
 test/python/transpiler/test_sabre_swap.py     |   2 +-
 .../python/transpiler/test_stochastic_swap.py |   2 +-
 7 files changed, 314 insertions(+), 118 deletions(-)
 create mode 100644 releasenotes/notes/use-target-in-generate-preset-pm-5215e00d22d0205c.yaml

diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py
index 95c583ceeaad..9dd316839a0f 100644
--- a/qiskit/compiler/transpiler.py
+++ b/qiskit/compiler/transpiler.py
@@ -13,7 +13,6 @@
 # pylint: disable=invalid-sequence-index
 
 """Circuit transpile function"""
-import copy
 import logging
 from time import time
 from typing import List, Union, Dict, Callable, Any, Optional, TypeVar
@@ -30,11 +29,10 @@
 from qiskit.transpiler import Layout, CouplingMap, PropertySet
 from qiskit.transpiler.basepasses import BasePass
 from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget
-from qiskit.transpiler.instruction_durations import InstructionDurations, InstructionDurationsType
+from qiskit.transpiler.instruction_durations import InstructionDurationsType
 from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
 from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
-from qiskit.transpiler.timing_constraints import TimingConstraints
-from qiskit.transpiler.target import Target, target_to_backend_properties
+from qiskit.transpiler.target import Target
 
 logger = logging.getLogger(__name__)
 
@@ -335,73 +333,32 @@ def callback_func(**kwargs):
             UserWarning,
         )
 
-    _skip_target = False
-    _given_inst_map = bool(inst_map)  # check before inst_map is overwritten
-    # If a target is specified, have it override any implicit selections from a backend
-    if target is not None:
-        if coupling_map is None:
-            coupling_map = target.build_coupling_map()
-        if basis_gates is None:
-            basis_gates = list(target.operation_names)
-        if instruction_durations is None:
-            instruction_durations = target.durations()
-        if inst_map is None:
-            inst_map = target.instruction_schedule_map()
-        if dt is None:
-            dt = target.dt
-        if timing_constraints is None:
-            timing_constraints = target.timing_constraints()
-        if backend_properties is None:
-            backend_properties = target_to_backend_properties(target)
-    # If target is not specified and any hardware constraint object is
-    # manually specified, do not use the target from the backend as
-    # it is invalidated by a custom basis gate list, custom coupling map,
-    # custom dt or custom instruction_durations
-    elif (
-        basis_gates is not None  # pylint: disable=too-many-boolean-expressions
-        or coupling_map is not None
-        or dt is not None
-        or instruction_durations is not None
-        or backend_properties is not None
-        or timing_constraints is not None
-    ):
-        _skip_target = True
-    else:
-        target = getattr(backend, "target", None)
+    if not ignore_backend_supplied_default_methods:
+        if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
+            scheduling_method = backend.get_scheduling_stage_plugin()
+        if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
+            translation_method = backend.get_translation_stage_plugin()
 
     initial_layout = _parse_initial_layout(initial_layout)
-    coupling_map = _parse_coupling_map(coupling_map, backend)
     approximation_degree = _parse_approximation_degree(approximation_degree)
-
     output_name = _parse_output_name(output_name, circuits)
-    inst_map = _parse_inst_map(inst_map, backend)
 
+    coupling_map = _parse_coupling_map(coupling_map)
     _check_circuits_coupling_map(circuits, coupling_map, backend)
 
-    timing_constraints = _parse_timing_constraints(backend, timing_constraints)
-    instruction_durations = _parse_instruction_durations(backend, instruction_durations, dt)
-
-    if _given_inst_map and inst_map.has_custom_gate() and target is not None:
-        # Do not mutate backend target
-        target = copy.deepcopy(target)
-        target.update_from_instruction_schedule_map(inst_map)
-
-    if not ignore_backend_supplied_default_methods:
-        if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
-            scheduling_method = backend.get_scheduling_stage_plugin()
-        if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
-            translation_method = backend.get_translation_stage_plugin()
-
+    # Edge cases require using the old model (loose constraints) instead of building a target,
+    # but we don't populate the passmanager config with loose constraints unless it's one of
+    # the known edge cases to control the execution path.
     pm = generate_preset_pass_manager(
         optimization_level,
-        backend=backend,
         target=target,
+        backend=backend,
         basis_gates=basis_gates,
-        inst_map=inst_map,
         coupling_map=coupling_map,
         instruction_durations=instruction_durations,
         backend_properties=backend_properties,
         timing_constraints=timing_constraints,
+        inst_map=inst_map,
         initial_layout=initial_layout,
         layout_method=layout_method,
         routing_method=routing_method,
@@ -414,14 +371,15 @@ def callback_func(**kwargs):
         hls_config=hls_config,
         init_method=init_method,
         optimization_method=optimization_method,
-        _skip_target=_skip_target,
+        dt=dt,
     )
+
     out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)
+
     for name, circ in zip(output_name, out_circuits):
         circ.name = name
     end_time = time()
     _log_transpile_time(start_time, end_time)
-
     if arg_circuits_list:
         return out_circuits
     else:
@@ -451,31 +409,20 @@ def _log_transpile_time(start_time, end_time):
     logger.info(log_msg)
 
 
-def _parse_inst_map(inst_map, backend):
-    # try getting inst_map from user, else backend
-    if inst_map is None and backend is not None:
-        inst_map = backend.target.instruction_schedule_map()
-    return inst_map
-
-
-def _parse_coupling_map(coupling_map, backend):
-    # try getting coupling_map from user, else backend
-    if coupling_map is None and backend is not None:
-        coupling_map = backend.coupling_map
-
+def _parse_coupling_map(coupling_map):
     # coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
-    if coupling_map is None or isinstance(coupling_map, CouplingMap):
-        return coupling_map
     if isinstance(coupling_map, list) and all(
         isinstance(i, list) and len(i) == 2 for i in coupling_map
     ):
         return CouplingMap(coupling_map)
-    else:
+    elif isinstance(coupling_map, list):
         raise TranspilerError(
             "Only a single input coupling map can be used with transpile() if you need to "
             "target different coupling maps for different circuits you must call transpile() "
             "multiple times"
         )
+    else:
+        return coupling_map
 
 
 def _parse_initial_layout(initial_layout):
@@ -491,22 +438,6 @@ def _parse_initial_layout(initial_layout):
     return initial_layout
 
 
-def _parse_instruction_durations(backend, inst_durations, dt):
-    """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided,
-    the backend will be ignored, otherwise, the durations will be populated from the
-    backend.
-    """
-    final_durations = InstructionDurations()
-    if not inst_durations:
-        backend_durations = InstructionDurations()
-        if backend is not None:
-            backend_durations = backend.instruction_durations
-        final_durations.update(backend_durations, dt or backend_durations.dt)
-    else:
-        final_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None))
-    return final_durations
-
-
 def _parse_approximation_degree(approximation_degree):
     if approximation_degree is None:
         return None
@@ -549,13 +480,3 @@ def _parse_output_name(output_name, circuits):
             )
     else:
         return [circuit.name for circuit in circuits]
-
-
-def _parse_timing_constraints(backend, timing_constraints):
-    if isinstance(timing_constraints, TimingConstraints):
-        return timing_constraints
-    if backend is None and timing_constraints is None:
-        timing_constraints = TimingConstraints()
-    elif backend is not None:
-        timing_constraints = backend.target.timing_constraints()
-    return timing_constraints
diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py
index 8d653ed3a1a9..37b284a4680f 100644
--- a/qiskit/transpiler/preset_passmanagers/__init__.py
+++ b/qiskit/transpiler/preset_passmanagers/__init__.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
@@ -21,7 +21,8 @@
 for the transpiler. The preset pass managers are instances of
 :class:`~.StagedPassManager` which are used to execute the circuit
 transformations as part of Qiskit's compiler inside the
-:func:`~.transpile` function at the different optimization levels.
+:func:`~.transpile` function at the different optimization levels, but
+can also be used in a standalone manner.
 The functionality here is divided into two parts, the first includes the
 functions used generate the entire pass manager which is used by
 :func:`~.transpile` (:ref:`preset_pass_manager_generators`) and the
@@ -56,13 +57,21 @@
 .. autofunction:: generate_scheduling
 .. currentmodule:: qiskit.transpiler.preset_passmanagers
 """
+import copy
 
-import warnings
+from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
+from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
+from qiskit.providers.backend_compat import BackendV2Converter
+
+from qiskit.transpiler.instruction_durations import InstructionDurations
+from qiskit.transpiler.timing_constraints import TimingConstraints
 
 from qiskit.transpiler.passmanager_config import PassManagerConfig
-from qiskit.transpiler.target import target_to_backend_properties
+from qiskit.transpiler.target import Target, target_to_backend_properties
 from qiskit.transpiler import CouplingMap
 
+from qiskit.transpiler.exceptions import TranspilerError
+
 from .level0 import level_0_pass_manager
 from .level1 import level_1_pass_manager
 from .level2 import level_2_pass_manager
@@ -91,16 +100,43 @@ def generate_preset_pass_manager(
     hls_config=None,
     init_method=None,
     optimization_method=None,
+    dt=None,
     *,
     _skip_target=False,
 ):
     """Generate a preset :class:`~.PassManager`
 
-    This function is used to quickly generate a preset pass manager. A preset pass
-    manager are the default pass managers used by the :func:`~.transpile`
+    This function is used to quickly generate a preset pass manager. Preset pass
+    managers are the default pass managers used by the :func:`~.transpile`
     function. This function provides a convenient and simple method to construct
     a standalone :class:`~.PassManager` object that mirrors what the transpile
+    function internally builds and uses.
+
+    The target constraints for the pass manager construction can be specified through a :class:`.Target`
+    instance, a `.BackendV1` or `.BackendV2` instance, or via loose constraints (``basis_gates``,
+    ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``,
+    ``dt`` or ``timing_constraints``).
+    The order of priorities for target constraints works as follows: if a ``target``
+    input is provided, it will take priority over any ``backend`` input or loose constraints.
+    If a ``backend`` is provided together with any loose constraint
+    from the list above, the loose constraint will take priority over the corresponding backend
+    constraint. This behavior is independent of whether the ``backend`` instance is of type
+    :class:`.BackendV1` or :class:`.BackendV2`, as summarized in the table below. The first column
+    in the table summarizes the potential user-provided constraints, and each cell shows whether
+    the priority is assigned to that specific constraint input or another input
+    (`target`/`backend(V1)`/`backend(V2)`).
 
+    ============================ ========= ======================== =======================
+    User Provided                target    backend(V1)              backend(V2)
+    ============================ ========= ======================== =======================
+    **basis_gates**              target    basis_gates              basis_gates
+    **coupling_map**             target    coupling_map             coupling_map
+    **instruction_durations**    target    instruction_durations    instruction_durations
+    **inst_map**                 target    inst_map                 inst_map
+    **dt**                       target    dt                       dt
+    **timing_constraints**       target    timing_constraints       timing_constraints
+    **backend_properties**       target    backend_properties       backend_properties
+    ============================ ========= ======================== =======================
 
     Args:
         optimization_level (int): The optimization level to generate a
@@ -126,16 +162,57 @@ def generate_preset_pass_manager(
             and ``backend_properties``.
         basis_gates (list): List of basis gate names to unroll to
             (e.g: ``['u1', 'u2', 'u3', 'cx']``).
-        inst_map (InstructionScheduleMap): Mapping object that maps gate to schedules.
+        inst_map (InstructionScheduleMap): Mapping object that maps gates to schedules.
             If any user defined calibration is found in the map and this is used in a
             circuit, transpiler attaches the custom gate definition to the circuit.
             This enables one to flexibly override the low-level instruction
             implementation.
         coupling_map (CouplingMap or list): Directed graph represented a coupling
-            map.
-        instruction_durations (InstructionDurations): Dictionary of duration
-            (in dt) for each instruction.
+            map. Multiple formats are supported:
+
+            #. ``CouplingMap`` instance
+            #. List, must be given as an adjacency matrix, where each entry
+               specifies all directed two-qubit interactions supported by backend,
+               e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]``
+
+        instruction_durations (InstructionDurations or list): Dictionary of duration
+            (in dt) for each instruction. If specified, these durations overwrite the
+            gate lengths in ``backend.properties``. Applicable only if ``scheduling_method``
+            is specified.
+            The format of ``instruction_durations`` must be as follows:
+            They must be given as an :class:`.InstructionDurations` instance or a list of tuples
+
+            ```
+            [(instruction_name, qubits, duration, unit), ...].
+            | [('cx', [0, 1], 12.3, 'ns'), ('u3', [0], 4.56, 'ns')]
+            | [('cx', [0, 1], 1000), ('u3', [0], 300)]
+            ```
+
+            If ``unit`` is omitted, the default is ``'dt'``, which is a sample time depending on backend.
+            If the time unit is ``'dt'``, the duration must be an integer.
+        dt (float): Backend sample time (resolution) in seconds.
+            If provided, this value will overwrite the ``dt`` value in ``instruction_durations``.
+            If ``None`` (default) and a backend is provided, ``backend.dt`` is used.
         timing_constraints (TimingConstraints): Hardware time alignment restrictions.
+             A quantum computer backend may report a set of restrictions, namely:
+
+                - granularity: An integer value representing minimum pulse gate
+                  resolution in units of ``dt``. A user-defined pulse gate should have
+                  duration of a multiple of this granularity value.
+                - min_length: An integer value representing minimum pulse gate
+                  length in units of ``dt``. A user-defined pulse gate should be longer
+                  than this length.
+                - pulse_alignment: An integer value representing a time resolution of gate
+                  instruction starting time. Gate instruction should start at time which
+                  is a multiple of the alignment value.
+                - acquire_alignment: An integer value representing a time resolution of measure
+                  instruction starting time. Measure instruction should start at time which
+                  is a multiple of the alignment value.
+
+                This information will be provided by the backend configuration.
+                If the backend doesn't have any restriction on the instruction time allocation,
+                then ``timing_constraints`` is None and no adjustment will be performed.
+
         initial_layout (Layout | List[int]): Initial position of virtual qubits on
             physical qubits.
         layout_method (str): The :class:`~.Pass` to use for choosing initial qubit
@@ -205,8 +282,74 @@ def generate_preset_pass_manager(
         ValueError: if an invalid value for ``optimization_level`` is passed in.
     """
 
-    if coupling_map is not None and not isinstance(coupling_map, CouplingMap):
-        coupling_map = CouplingMap(coupling_map)
+    if backend is not None and getattr(backend, "version", 0) <= 1:
+        # This is a temporary conversion step to allow for a smoother transition
+        # to a fully target-based transpiler pipeline while maintaining the behavior
+        # of `transpile` with BackendV1 inputs.
+        backend = BackendV2Converter(backend)
+
+    # Check if a custom inst_map was specified before overwriting inst_map
+    _given_inst_map = bool(inst_map)
+    # If there are no loose constraints => use backend target if available
+    _no_loose_constraints = (
+        basis_gates is None
+        and coupling_map is None
+        and dt is None
+        and instruction_durations is None
+        and backend_properties is None
+        and timing_constraints is None
+    )
+    # If it's an edge case => do not build target
+    _skip_target = (
+        target is None
+        and backend is None
+        and (basis_gates is None or coupling_map is None or instruction_durations is not None)
+    )
+
+    # Resolve loose constraints case-by-case against backend constraints.
+    # The order of priority is loose constraints > backend.
+    dt = _parse_dt(dt, backend)
+    instruction_durations = _parse_instruction_durations(backend, instruction_durations, dt)
+    timing_constraints = _parse_timing_constraints(backend, timing_constraints)
+    inst_map = _parse_inst_map(inst_map, backend)
+    # The basis gates parser will set _skip_target to True if a custom basis gate is found
+    # (known edge case).
+    basis_gates, name_mapping, _skip_target = _parse_basis_gates(
+        basis_gates, backend, inst_map, _skip_target
+    )
+    coupling_map = _parse_coupling_map(coupling_map, backend)
+
+    if target is None:
+        if backend is not None and _no_loose_constraints:
+            # If a backend is specified without loose constraints, use its target directly.
+            target = backend.target
+        elif not _skip_target:
+            # Only parse backend properties when the target isn't skipped to
+            # preserve the former behavior of transpile.
+            backend_properties = _parse_backend_properties(backend_properties, backend)
+            # Build target from constraints.
+            target = Target.from_configuration(
+                basis_gates=basis_gates,
+                num_qubits=backend.num_qubits if backend is not None else None,
+                coupling_map=coupling_map,
+                # If the instruction map has custom gates, do not give as config, the information
+                # will be added to the target with update_from_instruction_schedule_map
+                inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None,
+                backend_properties=backend_properties,
+                instruction_durations=instruction_durations,
+                concurrent_measurements=(
+                    backend.target.concurrent_measurements if backend is not None else None
+                ),
+                dt=dt,
+                timing_constraints=timing_constraints,
+                custom_name_mapping=name_mapping,
+            )
+
+    # Update target with custom gate information. Note that this is an exception to the priority
+    # order (target > loose constraints), added to handle custom gates for scheduling passes.
+    if target is not None and _given_inst_map and inst_map.has_custom_gate():
+        target = copy.deepcopy(target)
+        target.update_from_instruction_schedule_map(inst_map)
 
     if target is not None:
         if coupling_map is None:
@@ -262,6 +405,119 @@ def generate_preset_pass_manager(
     return pm
 
 
+def _parse_basis_gates(basis_gates, backend, inst_map, skip_target):
+    name_mapping = {}
+    standard_gates = get_standard_gate_name_mapping()
+    # Add control flow gates by default to basis set
+    default_gates = {"measure", "delay", "reset"}.union(CONTROL_FLOW_OP_NAMES)
+
+    try:
+        instructions = set(basis_gates)
+        for name in default_gates:
+            if name not in instructions:
+                instructions.add(name)
+    except TypeError:
+        instructions = None
+
+    if backend is None:
+        # Check for custom instructions
+        if instructions is None:
+            return None, name_mapping, skip_target
+
+        for inst in instructions:
+            if inst not in standard_gates or inst not in default_gates:
+                skip_target = True
+                break
+
+        return list(instructions), name_mapping, skip_target
+
+    instructions = instructions or backend.operation_names
+    name_mapping.update(
+        {name: backend.target.operation_from_name(name) for name in backend.operation_names}
+    )
+
+    # Check for custom instructions before removing calibrations
+    for inst in instructions:
+        if inst not in standard_gates or inst not in default_gates:
+            skip_target = True
+            break
+
+    # Remove calibrated instructions, as they will be added later from the instruction schedule map
+    if inst_map is not None and not skip_target:
+        for inst in inst_map.instructions:
+            for qubit in inst_map.qubits_with_instruction(inst):
+                entry = inst_map._get_calibration_entry(inst, qubit)
+                if entry.user_provided and inst in instructions:
+                    instructions.remove(inst)
+
+    return list(instructions) if instructions else None, name_mapping, skip_target
+
+
+def _parse_inst_map(inst_map, backend):
+    # try getting inst_map from user, else backend
+    if inst_map is None and backend is not None:
+        inst_map = backend.target.instruction_schedule_map()
+    return inst_map
+
+
+def _parse_backend_properties(backend_properties, backend):
+    # try getting backend_props from user, else backend
+    if backend_properties is None and backend is not None:
+        backend_properties = target_to_backend_properties(backend.target)
+    return backend_properties
+
+
+def _parse_dt(dt, backend):
+    # try getting dt from user, else backend
+    if dt is None and backend is not None:
+        dt = backend.target.dt
+    return dt
+
+
+def _parse_coupling_map(coupling_map, backend):
+    # try getting coupling_map from user, else backend
+    if coupling_map is None and backend is not None:
+        coupling_map = backend.coupling_map
+
+    # coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
+    if coupling_map is None or isinstance(coupling_map, CouplingMap):
+        return coupling_map
+    if isinstance(coupling_map, list) and all(
+        isinstance(i, list) and len(i) == 2 for i in coupling_map
+    ):
+        return CouplingMap(coupling_map)
+    else:
+        raise TranspilerError(
+            "Only a single input coupling map can be used with generate_preset_pass_manager()."
+        )
+
+
+def _parse_instruction_durations(backend, inst_durations, dt):
+    """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided,
+    the backend will be ignored, otherwise, the durations will be populated from the
+    backend.
+    """
+    final_durations = InstructionDurations()
+    if not inst_durations:
+        backend_durations = InstructionDurations()
+        if backend is not None:
+            backend_durations = backend.instruction_durations
+        final_durations.update(backend_durations, dt or backend_durations.dt)
+    else:
+        final_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None))
+    return final_durations
+
+
+def _parse_timing_constraints(backend, timing_constraints):
+    if isinstance(timing_constraints, TimingConstraints):
+        return timing_constraints
+    if backend is None and timing_constraints is None:
+        timing_constraints = TimingConstraints()
+    elif backend is not None:
+        timing_constraints = backend.target.timing_constraints()
+    return timing_constraints
+
+
 __all__ = [
     "level_0_pass_manager",
     "level_1_pass_manager",
diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py
index 5af9e8686583..f7b5227c0266 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -426,7 +426,9 @@ def add_instruction(self, instruction, properties=None, name=None):
                         f"of qubits in the properties dictionary: {qarg}"
                     )
                 if qarg is not None:
-                    self.num_qubits = max(self.num_qubits, max(qarg) + 1)
+                    self.num_qubits = max(
+                        self.num_qubits if self.num_qubits is not None else 0, max(qarg) + 1
+                    )
                 qargs_val[qarg] = properties[qarg]
                 self._qarg_gate_map[qarg].add(instruction_name)
         self._gate_map[instruction_name] = qargs_val
@@ -987,7 +989,8 @@ def instruction_properties(self, index):
 
     def _build_coupling_graph(self):
         self._coupling_graph = rx.PyDiGraph(multigraph=False)
-        self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)])
+        if self.num_qubits is not None:
+            self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)])
         for gate, qarg_map in self._gate_map.items():
             if qarg_map is None:
                 if self._gate_name_map[gate].num_qubits == 2:
diff --git a/releasenotes/notes/use-target-in-generate-preset-pm-5215e00d22d0205c.yaml b/releasenotes/notes/use-target-in-generate-preset-pm-5215e00d22d0205c.yaml
new file mode 100644
index 000000000000..4857bb1bda12
--- /dev/null
+++ b/releasenotes/notes/use-target-in-generate-preset-pm-5215e00d22d0205c.yaml
@@ -0,0 +1,14 @@
+---
+features_transpiler:
+  - |
+    A new ``dt`` argument has been added to :func:`.generate_preset_pass_manager` to match
+    the set of arguments of :func:`.transpile`. This will allow for the internal conversion 
+    of transpilation constraints to a :class:`.Target` representation.
+    
+upgrade_transpiler:
+  - |
+    The :func:`.generate_preset_pass_manager` function has been upgraded to, when possible,
+    internally convert transpiler constraints into a :class:`.Target` instance.
+    If a `backend` input of type :class:`.BackendV1` is provided, it will be 
+    converted to :class:`.BackendV2` to expose its :class:`.Target`. This change does 
+    not require any user action.
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
index 6166528f8868..30ba83440c9e 100644
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -1829,7 +1829,7 @@ def test_synthesis_translation_method_with_single_qubit_gates(self, optimization
 
     @data(0, 1, 2, 3)
     def test_synthesis_translation_method_with_gates_outside_basis(self, optimization_level):
-        """Test that synthesis translation works for circuits with single gates outside bassis"""
+        """Test that synthesis translation works for circuits with single gates outside basis"""
         qc = QuantumCircuit(2)
         qc.swap(0, 1)
         res = transpile(
@@ -2781,12 +2781,14 @@ def test_backend_and_custom_gate(self, opt_level):
         backend = GenericBackendV2(
             num_qubits=5,
             coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]],
+            seed=42,
         )
         inst_map = InstructionScheduleMap()
         inst_map.add("newgate", [0, 1], pulse.ScheduleBlock())
         newgate = Gate("newgate", 2, [])
         circ = QuantumCircuit(2)
         circ.append(newgate, [0, 1])
+
         tqc = transpile(
             circ,
             backend,
@@ -2797,8 +2799,8 @@ def test_backend_and_custom_gate(self, opt_level):
         )
         self.assertEqual(len(tqc.data), 1)
         self.assertEqual(tqc.data[0].operation, newgate)
-        qubits = tuple(tqc.find_bit(x).index for x in tqc.data[0].qubits)
-        self.assertIn(qubits, backend.target.qargs)
+        for x in tqc.data[0].qubits:
+            self.assertIn((tqc.find_bit(x).index,), backend.target.qargs)
 
 
 @ddt
diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py
index 5315c4b8e018..fbe4e1fbf74c 100644
--- a/test/python/transpiler/test_sabre_swap.py
+++ b/test/python/transpiler/test_sabre_swap.py
@@ -1329,9 +1329,9 @@ def setUpClass(cls):
         super().setUpClass()
         cls.backend = Fake27QPulseV1()
         cls.backend.configuration().coupling_map = MUMBAI_CMAP
+        cls.backend.configuration().basis_gates += ["for_loop", "while_loop", "if_else"]
         cls.coupling_edge_set = {tuple(x) for x in cls.backend.configuration().coupling_map}
         cls.basis_gates = set(cls.backend.configuration().basis_gates)
-        cls.basis_gates.update(["for_loop", "while_loop", "if_else"])
 
     def assert_valid_circuit(self, transpiled):
         """Assert circuit complies with constraints of backend."""
diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py
index 5b924e590a54..8c96150ae8ff 100644
--- a/test/python/transpiler/test_stochastic_swap.py
+++ b/test/python/transpiler/test_stochastic_swap.py
@@ -1489,9 +1489,9 @@ class TestStochasticSwapRandomCircuitValidOutput(QiskitTestCase):
     def setUpClass(cls):
         super().setUpClass()
         cls.backend = Fake27QPulseV1()
+        cls.backend.configuration().basis_gates += ["for_loop", "while_loop", "if_else"]
         cls.coupling_edge_set = {tuple(x) for x in cls.backend.configuration().coupling_map}
         cls.basis_gates = set(cls.backend.configuration().basis_gates)
-        cls.basis_gates.update(["for_loop", "while_loop", "if_else"])
 
     def assert_valid_circuit(self, transpiled):
         """Assert circuit complies with constraints of backend."""

From 9d03b4bf610e41c17227ecefda7feba5262d348e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Quinten=20Prei=C3=9F?=
 <90014830+J-C-Q@users.noreply.github.com>
Date: Sat, 1 Jun 2024 04:47:39 +0200
Subject: [PATCH 120/179] Update operation.py docs (#12485)

One "add" too much :)
---
 qiskit/circuit/operation.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit/circuit/operation.py b/qiskit/circuit/operation.py
index c299e130178a..8856222b2663 100644
--- a/qiskit/circuit/operation.py
+++ b/qiskit/circuit/operation.py
@@ -25,7 +25,7 @@ class Operation(ABC):
     :class:`~qiskit.circuit.Reset`, :class:`~qiskit.circuit.Barrier`,
     :class:`~qiskit.circuit.Measure`, and operators such as :class:`~qiskit.quantum_info.Clifford`.
 
-    The main purpose is to add allow abstract mathematical objects to be added directly onto
+    The main purpose is to allow abstract mathematical objects to be added directly onto
     abstract circuits, and for the exact syntheses of these to be determined later, during
     compilation.
 

From 797bb285632a4b58c35c9e4cf5be44502b5bd78a Mon Sep 17 00:00:00 2001
From: Pieter Eendebak 
Date: Tue, 4 Jun 2024 01:39:55 +0200
Subject: [PATCH 121/179] Add __pos__ for Parameter (#12496)

* Add __pos__ for ParameterExpression

* docstring

* Update qiskit/circuit/parameterexpression.py

Co-authored-by: Jake Lishman 

* Add release note

* replace 1.0 by 1

* black

* Reword release note

---------

Co-authored-by: Jake Lishman 
Co-authored-by: Jake Lishman 
---
 qiskit/circuit/parameterexpression.py                      | 5 ++++-
 ...unary_pos_for_parameterexpression-6421421b6dc20fbb.yaml | 4 ++++
 test/python/circuit/test_parameters.py                     | 7 +++++++
 3 files changed, 15 insertions(+), 1 deletion(-)
 create mode 100644 releasenotes/notes/unary_pos_for_parameterexpression-6421421b6dc20fbb.yaml

diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
index 4f6453f90f4c..2b81ddd769f6 100644
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -327,8 +327,11 @@ def __rsub__(self, other):
     def __mul__(self, other):
         return self._apply_operation(operator.mul, other)
 
+    def __pos__(self):
+        return self._apply_operation(operator.mul, 1)
+
     def __neg__(self):
-        return self._apply_operation(operator.mul, -1.0)
+        return self._apply_operation(operator.mul, -1)
 
     def __rmul__(self, other):
         return self._apply_operation(operator.mul, other, reflected=True)
diff --git a/releasenotes/notes/unary_pos_for_parameterexpression-6421421b6dc20fbb.yaml b/releasenotes/notes/unary_pos_for_parameterexpression-6421421b6dc20fbb.yaml
new file mode 100644
index 000000000000..92fc63d49892
--- /dev/null
+++ b/releasenotes/notes/unary_pos_for_parameterexpression-6421421b6dc20fbb.yaml
@@ -0,0 +1,4 @@
+---
+features_circuits:
+  - |
+    :class:`.ParameterExpression` now supports the unary ``+`` operator.
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
index 7bcc2cd35f3a..ed82f33eac97 100644
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -1755,6 +1755,13 @@ def test_negated_expression(self):
 
         self.assertEqual(float(bound_expr2), 3)
 
+    def test_positive_expression(self):
+        """This tests parameter unary plus."""
+        x = Parameter("x")
+        y = +x
+        self.assertEqual(float(y.bind({x: 1})), 1.0)
+        self.assertIsInstance(+x, type(-x))
+
     def test_standard_cu3(self):
         """This tests parameter negation in standard extension gate cu3."""
         from qiskit.circuit.library import CU3Gate

From 72ff2cf20e1047236c15f27f08c384c40f4a82f8 Mon Sep 17 00:00:00 2001
From: John Lapeyre 
Date: Tue, 4 Jun 2024 14:12:06 -0400
Subject: [PATCH 122/179] Implement `num_qubits` and `num_clbits` in Rust
 (#12495)

* Implement num_qubits and num_clbits in Rust

* QuantumCircuit.num_qubits and num_clbits are twice as fast after
  this PR.
* These are used in several places. For example QuantumCircuit.width()
  is three times faster.
* num_qubits and num_clbits are introduced in circuit_data.rs. These
  functions are called by the corresponding Python methods.
  They are also used in circuit_data.rs itself.

* Add doc strings for rust implemented num_qubits and num_clbits

* Add doc string for `width` in Rust
---
 crates/circuit/src/circuit_data.rs | 49 ++++++++++++++++++++++++------
 qiskit/circuit/quantumcircuit.py   | 28 ++++++++---------
 2 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs
index 944565cf36d8..d8eca9ef8546 100644
--- a/crates/circuit/src/circuit_data.rs
+++ b/crates/circuit/src/circuit_data.rs
@@ -220,6 +220,16 @@ impl CircuitData {
         self.qubits.clone_ref(py)
     }
 
+    /// Return the number of qubits. This is equivalent to the length of the list returned by
+    /// :meth:`.CircuitData.qubits`
+    ///
+    /// Returns:
+    ///     int: The number of qubits.
+    #[getter]
+    pub fn num_qubits(&self) -> usize {
+        self.qubits_native.len()
+    }
+
     /// Returns the current sequence of registered :class:`.Clbit`
     /// instances as a list.
     ///
@@ -235,6 +245,25 @@ impl CircuitData {
         self.clbits.clone_ref(py)
     }
 
+    /// Return the number of clbits. This is equivalent to the length of the list returned by
+    /// :meth:`.CircuitData.clbits`.
+    ///
+    /// Returns:
+    ///     int: The number of clbits.
+    #[getter]
+    pub fn num_clbits(&self) -> usize {
+        self.clbits_native.len()
+    }
+
+    /// Return the width of the circuit. This is the number of qubits plus the
+    /// number of clbits.
+    ///
+    /// Returns:
+    ///     int: The width of the circuit.
+    pub fn width(&self) -> usize {
+        self.num_qubits() + self.num_clbits()
+    }
+
     /// Registers a :class:`.Qubit` instance.
     ///
     /// Args:
@@ -246,13 +275,13 @@ impl CircuitData {
     ///         was provided.
     #[pyo3(signature = (bit, *, strict=true))]
     pub fn add_qubit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> {
-        if self.qubits_native.len() != self.qubits.bind(bit.py()).len() {
+        if self.num_qubits() != self.qubits.bind(bit.py()).len() {
             return Err(PyRuntimeError::new_err(concat!(
                 "This circuit's 'qubits' list has become out of sync with the circuit data.",
                 " Did something modify it?"
             )));
         }
-        let idx: BitType = self.qubits_native.len().try_into().map_err(|_| {
+        let idx: BitType = self.num_qubits().try_into().map_err(|_| {
             PyRuntimeError::new_err(
                 "The number of qubits in the circuit has exceeded the maximum capacity",
             )
@@ -284,13 +313,13 @@ impl CircuitData {
     ///         was provided.
     #[pyo3(signature = (bit, *, strict=true))]
     pub fn add_clbit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> {
-        if self.clbits_native.len() != self.clbits.bind(bit.py()).len() {
+        if self.num_clbits() != self.clbits.bind(bit.py()).len() {
             return Err(PyRuntimeError::new_err(concat!(
                 "This circuit's 'clbits' list has become out of sync with the circuit data.",
                 " Did something modify it?"
             )));
         }
-        let idx: BitType = self.clbits_native.len().try_into().map_err(|_| {
+        let idx: BitType = self.num_clbits().try_into().map_err(|_| {
             PyRuntimeError::new_err(
                 "The number of clbits in the circuit has exceeded the maximum capacity",
             )
@@ -460,11 +489,11 @@ impl CircuitData {
     ) -> PyResult<()> {
         let mut temp = CircuitData::new(py, qubits, clbits, None, 0)?;
         if qubits.is_some() {
-            if temp.qubits_native.len() < self.qubits_native.len() {
+            if temp.num_qubits() < self.num_qubits() {
                 return Err(PyValueError::new_err(format!(
                     "Replacement 'qubits' of size {:?} must contain at least {:?} bits.",
-                    temp.qubits_native.len(),
-                    self.qubits_native.len(),
+                    temp.num_qubits(),
+                    self.num_qubits(),
                 )));
             }
             std::mem::swap(&mut temp.qubits, &mut self.qubits);
@@ -475,11 +504,11 @@ impl CircuitData {
             );
         }
         if clbits.is_some() {
-            if temp.clbits_native.len() < self.clbits_native.len() {
+            if temp.num_clbits() < self.num_clbits() {
                 return Err(PyValueError::new_err(format!(
                     "Replacement 'clbits' of size {:?} must contain at least {:?} bits.",
-                    temp.clbits_native.len(),
-                    self.clbits_native.len(),
+                    temp.num_clbits(),
+                    self.num_clbits(),
                 )));
             }
             std::mem::swap(&mut temp.clbits, &mut self.clbits);
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index a157f04375a3..73c5101bb469 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1919,10 +1919,10 @@ def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var:
             edge_map.update(zip(other.qubits, dest.qubits))
         else:
             mapped_qubits = dest.qbit_argument_conversion(qubits)
-            if len(mapped_qubits) != len(other.qubits):
+            if len(mapped_qubits) != other.num_qubits:
                 raise CircuitError(
                     f"Number of items in qubits parameter ({len(mapped_qubits)}) does not"
-                    f" match number of qubits in the circuit ({len(other.qubits)})."
+                    f" match number of qubits in the circuit ({other.num_qubits})."
                 )
             if len(set(mapped_qubits)) != len(mapped_qubits):
                 raise CircuitError(
@@ -1935,10 +1935,10 @@ def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var:
             edge_map.update(zip(other.clbits, dest.clbits))
         else:
             mapped_clbits = dest.cbit_argument_conversion(clbits)
-            if len(mapped_clbits) != len(other.clbits):
+            if len(mapped_clbits) != other.num_clbits:
                 raise CircuitError(
                     f"Number of items in clbits parameter ({len(mapped_clbits)}) does not"
-                    f" match number of clbits in the circuit ({len(other.clbits)})."
+                    f" match number of clbits in the circuit ({other.num_clbits})."
                 )
             if len(set(mapped_clbits)) != len(mapped_clbits):
                 raise CircuitError(
@@ -2917,7 +2917,7 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None:
                     else:
                         self._data.add_qubit(bit)
                         self._qubit_indices[bit] = BitLocations(
-                            len(self._data.qubits) - 1, [(register, idx)]
+                            self._data.num_qubits - 1, [(register, idx)]
                         )
 
             elif isinstance(register, ClassicalRegister):
@@ -2929,7 +2929,7 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None:
                     else:
                         self._data.add_clbit(bit)
                         self._clbit_indices[bit] = BitLocations(
-                            len(self._data.clbits) - 1, [(register, idx)]
+                            self._data.num_clbits - 1, [(register, idx)]
                         )
 
             elif isinstance(register, list):
@@ -2950,10 +2950,10 @@ def add_bits(self, bits: Iterable[Bit]) -> None:
                 self._ancillas.append(bit)
             if isinstance(bit, Qubit):
                 self._data.add_qubit(bit)
-                self._qubit_indices[bit] = BitLocations(len(self._data.qubits) - 1, [])
+                self._qubit_indices[bit] = BitLocations(self._data.num_qubits - 1, [])
             elif isinstance(bit, Clbit):
                 self._data.add_clbit(bit)
-                self._clbit_indices[bit] = BitLocations(len(self._data.clbits) - 1, [])
+                self._clbit_indices[bit] = BitLocations(self._data.num_clbits - 1, [])
             else:
                 raise CircuitError(
                     "Expected an instance of Qubit, Clbit, or "
@@ -3393,12 +3393,12 @@ def width(self) -> int:
             int: Width of circuit.
 
         """
-        return len(self.qubits) + len(self.clbits)
+        return self._data.width()
 
     @property
     def num_qubits(self) -> int:
         """Return number of qubits."""
-        return len(self.qubits)
+        return self._data.num_qubits
 
     @property
     def num_ancillas(self) -> int:
@@ -3408,7 +3408,7 @@ def num_ancillas(self) -> int:
     @property
     def num_clbits(self) -> int:
         """Return number of classical bits."""
-        return len(self.clbits)
+        return self._data.num_clbits
 
     # The stringified return type is because OrderedDict can't be subscripted before Python 3.9, and
     # typing.OrderedDict wasn't added until 3.7.2.  It can be turned into a proper type once 3.6
@@ -3879,18 +3879,18 @@ def measure_all(
         else:
             circ = self.copy()
         if add_bits:
-            new_creg = circ._create_creg(len(circ.qubits), "meas")
+            new_creg = circ._create_creg(circ.num_qubits, "meas")
             circ.add_register(new_creg)
             circ.barrier()
             circ.measure(circ.qubits, new_creg)
         else:
-            if len(circ.clbits) < len(circ.qubits):
+            if circ.num_clbits < circ.num_qubits:
                 raise CircuitError(
                     "The number of classical bits must be equal or greater than "
                     "the number of qubits."
                 )
             circ.barrier()
-            circ.measure(circ.qubits, circ.clbits[0 : len(circ.qubits)])
+            circ.measure(circ.qubits, circ.clbits[0 : circ.num_qubits])
 
         if not inplace:
             return circ

From 9f4a474e97d356163caa6fac430ce28313e8ed51 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 6 Jun 2024 09:36:10 -0400
Subject: [PATCH 123/179] Fix version table in qpy docs (#12512)

The QPY docs included a format version table that matched up the Qiskit
releases to the supported QPY format versions for that release. However,
a typo resulted in it being treated as a comment instead of table this
commit fixes this and a small copy paste error in one of the versions
listed in the table.
---
 qiskit/qpy/__init__.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py
index 4e7769106fc6..e072536fef47 100644
--- a/qiskit/qpy/__init__.py
+++ b/qiskit/qpy/__init__.py
@@ -127,7 +127,7 @@
 Qiskit (and qiskit-terra prior to Qiskit 1.0.0) release going back to the introduction
 of QPY in qiskit-terra 0.18.0.
 
-.. list-table: QPY Format Version History
+.. list-table:: QPY Format Version History
    :header-rows: 1
 
    * - Qiskit (qiskit-terra for < 1.0.0) version
@@ -138,7 +138,7 @@
      - 12
    * - 1.0.2
      - 10, 11
-     - 12
+     - 11
    * - 1.0.1
      - 10, 11
      - 11

From 767bd0733073e43b189cf63116ac124ea0868cf8 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 6 Jun 2024 15:13:59 +0100
Subject: [PATCH 124/179] Implement `__array__` for `qasm2._DefinedGate`
 (#12119)

* Implement `__array__` for `qasm2._DefinedGate`

Gates defined from OpenQASM 2 should have a well-defined matrix form (up
to global phase) whenever all the constituent parts of the definition
do. This manually makes such a matrix available.

* Fix signature for Numpy 2.0
---
 qiskit/qasm2/parse.py                         |  8 ++++++
 .../qasm2-to-matrix-c707fe1e61b3987f.yaml     |  9 +++++++
 test/python/qasm2/test_structure.py           | 26 +++++++++++++++++++
 3 files changed, 43 insertions(+)
 create mode 100644 releasenotes/notes/qasm2-to-matrix-c707fe1e61b3987f.yaml

diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py
index 5cb8137b5f0d..30c85843a361 100644
--- a/qiskit/qasm2/parse.py
+++ b/qiskit/qasm2/parse.py
@@ -16,6 +16,8 @@
 import math
 from typing import Iterable, Callable
 
+import numpy as np
+
 from qiskit.circuit import (
     Barrier,
     CircuitInstruction,
@@ -30,6 +32,7 @@
     Reset,
     library as lib,
 )
+from qiskit.quantum_info import Operator
 from qiskit._accelerate.qasm2 import (
     OpCode,
     UnaryOpCode,
@@ -315,6 +318,11 @@ def _define(self):
                 raise ValueError(f"received invalid bytecode to build gate: {op}")
         self._definition = qc
 
+    def __array__(self, dtype=None, copy=None):
+        if copy is False:
+            raise ValueError("unable to avoid copy while creating an array as requested")
+        return np.asarray(Operator(self.definition), dtype=dtype)
+
     # It's fiddly to implement pickling for PyO3 types (the bytecode stream), so instead if we need
     # to pickle ourselves, we just eagerly create the definition and pickle that.
 
diff --git a/releasenotes/notes/qasm2-to-matrix-c707fe1e61b3987f.yaml b/releasenotes/notes/qasm2-to-matrix-c707fe1e61b3987f.yaml
new file mode 100644
index 000000000000..84cfd1a1d351
--- /dev/null
+++ b/releasenotes/notes/qasm2-to-matrix-c707fe1e61b3987f.yaml
@@ -0,0 +1,9 @@
+---
+fixes:
+  - |
+    Custom gates (those stemming from a ``gate`` statement) in imported OpenQASM 2 programs will now
+    have an :meth:`.Gate.to_matrix` implementation.  Previously they would have no matrix definition,
+    meaning that roundtrips through OpenQASM 2 could needlessly lose the ability to derive the gate
+    matrix.  Note, though, that the matrix is calculated by recursively finding the matrices of the
+    inner gate definitions, as :class:`.Operator` does, which might be less performant than before
+    the round-trip.
diff --git a/test/python/qasm2/test_structure.py b/test/python/qasm2/test_structure.py
index 141d3c0f8b0c..22eff30b38f4 100644
--- a/test/python/qasm2/test_structure.py
+++ b/test/python/qasm2/test_structure.py
@@ -22,6 +22,7 @@
 import tempfile
 
 import ddt
+import numpy as np
 
 import qiskit.qasm2
 from qiskit import qpy
@@ -34,6 +35,7 @@
     Qubit,
     library as lib,
 )
+from qiskit.quantum_info import Operator
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 from . import gate_builder
@@ -906,6 +908,30 @@ def test_conditioned_broadcast_against_empty_register(self):
         )
         self.assertEqual(parsed, qc)
 
+    def test_has_to_matrix(self):
+        program = """
+            OPENQASM 2.0;
+            include "qelib1.inc";
+            qreg qr[1];
+            gate my_gate(a) q {
+                rz(a) q;
+                rx(pi / 2) q;
+                rz(-a) q;
+            }
+            my_gate(1.0) qr[0];
+        """
+        parsed = qiskit.qasm2.loads(program)
+        expected = (
+            lib.RZGate(-1.0).to_matrix()
+            @ lib.RXGate(math.pi / 2).to_matrix()
+            @ lib.RZGate(1.0).to_matrix()
+        )
+        defined_gate = parsed.data[0].operation
+        self.assertEqual(defined_gate.name, "my_gate")
+        np.testing.assert_allclose(defined_gate.to_matrix(), expected, atol=1e-14, rtol=0)
+        # Also test that the standard `Operator` method on the whole circuit still works.
+        np.testing.assert_allclose(Operator(parsed), expected, atol=1e-14, rtol=0)
+
 
 class TestReset(QiskitTestCase):
     def test_single(self):

From 778a6b24baf46285e4576e1e41b11443249f9838 Mon Sep 17 00:00:00 2001
From: Catherine Lozano 
Date: Thu, 6 Jun 2024 15:20:57 -0400
Subject: [PATCH 125/179] Removed deprecated bench test (#12522)

---
 test/benchmarks/isometry.py | 69 -------------------------------------
 1 file changed, 69 deletions(-)
 delete mode 100644 test/benchmarks/isometry.py

diff --git a/test/benchmarks/isometry.py b/test/benchmarks/isometry.py
deleted file mode 100644
index c3cf13e4d0e6..000000000000
--- a/test/benchmarks/isometry.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2023
-#
-# This code is licensed under the Apache License, Version 2.0. You may
-# obtain a copy of this license in the LICENSE.txt file in the root directory
-# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-#
-# Any modifications or derivative works of this code must retain this
-# copyright notice, and modified files need to carry a notice indicating
-# that they have been altered from the originals.
-
-# pylint: disable=missing-docstring,invalid-name,no-member
-# pylint: disable=attribute-defined-outside-init
-# pylint: disable=unused-argument
-
-from qiskit import QuantumRegister, QuantumCircuit
-from qiskit.compiler import transpile
-from qiskit.quantum_info.random import random_unitary
-from qiskit.circuit.library.generalized_gates import Isometry
-
-
-class IsometryTranspileBench:
-    params = ([0, 1, 2, 3], [3, 4, 5, 6])
-    param_names = ["number of input qubits", "number of output qubits"]
-
-    def setup(self, m, n):
-        q = QuantumRegister(n)
-        qc = QuantumCircuit(q)
-        if not hasattr(qc, "iso"):
-            raise NotImplementedError
-        iso = random_unitary(2**n, seed=0).data[:, 0 : 2**m]
-        if len(iso.shape) == 1:
-            iso = iso.reshape((len(iso), 1))
-        iso_gate = Isometry(iso, 0, 0)
-        qc.append(iso_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

From 1798c3f20868225f5416186e25b8e20e8b29199c Mon Sep 17 00:00:00 2001
From: Catherine Lozano 
Date: Thu, 6 Jun 2024 15:23:35 -0400
Subject: [PATCH 126/179] fixing deprecated methods and incorrect dag state
 (#12513)

* changed dag state for layout_2q and apply_layout to dag before layout in setup

* Removed CX tests using depreciated cx functions

* fixed formatting
---
 test/benchmarks/mapping_passes.py | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/test/benchmarks/mapping_passes.py b/test/benchmarks/mapping_passes.py
index 180925905d20..4f87323f33a8 100644
--- a/test/benchmarks/mapping_passes.py
+++ b/test/benchmarks/mapping_passes.py
@@ -124,12 +124,12 @@ def time_dense_layout(self, _, __):
     def time_layout_2q_distance(self, _, __):
         layout = Layout2qDistance(self.coupling_map)
         layout.property_set["layout"] = self.layout
-        layout.run(self.dag)
+        layout.run(self.enlarge_dag)
 
     def time_apply_layout(self, _, __):
         layout = ApplyLayout()
         layout.property_set["layout"] = self.layout
-        layout.run(self.dag)
+        layout.run(self.enlarge_dag)
 
     def time_full_ancilla_allocation(self, _, __):
         ancilla = FullAncillaAllocation(self.coupling_map)
@@ -232,12 +232,6 @@ def setup(self, n_qubits, depth):
         self.backend_props = Fake20QV1().properties()
         self.routed_dag = StochasticSwap(self.coupling_map, seed=42).run(self.dag)
 
-    def time_cxdirection(self, _, __):
-        CXDirection(self.coupling_map).run(self.routed_dag)
-
-    def time_check_cx_direction(self, _, __):
-        CheckCXDirection(self.coupling_map).run(self.routed_dag)
-
     def time_gate_direction(self, _, __):
         GateDirection(self.coupling_map).run(self.routed_dag)
 

From d1c8404dcadf487d26f048aa4ba4c148767349dd Mon Sep 17 00:00:00 2001
From: Kevin Hartman 
Date: Thu, 6 Jun 2024 16:29:23 -0400
Subject: [PATCH 127/179] [DAGCircuit Oxidation] Refactor bit management in
 `CircuitData` (#12372)

* Checkpoint before rebase.

* Refactor bit management in CircuitData.

* Revert changes not for this PR.

* Add doc comment for InstructionPacker, rename Interner::lookup.

* Add CircuitData::map_* and refactor.

* Fix merge issue.

* CircuitInstruction::new returns Self.

* Use unbind.

* Make bit types pub.

* Fix merge.
---
 crates/circuit/src/bit_data.rs            | 192 ++++++++++++
 crates/circuit/src/circuit_data.rs        | 338 +++++++---------------
 crates/circuit/src/circuit_instruction.rs |  42 ++-
 crates/circuit/src/intern_context.rs      |  71 -----
 crates/circuit/src/interner.rs            | 125 ++++++++
 crates/circuit/src/lib.rs                 |  37 ++-
 crates/circuit/src/packed_instruction.rs  |  25 ++
 7 files changed, 513 insertions(+), 317 deletions(-)
 create mode 100644 crates/circuit/src/bit_data.rs
 delete mode 100644 crates/circuit/src/intern_context.rs
 create mode 100644 crates/circuit/src/interner.rs
 create mode 100644 crates/circuit/src/packed_instruction.rs

diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs
new file mode 100644
index 000000000000..7964ec186e0f
--- /dev/null
+++ b/crates/circuit/src/bit_data.rs
@@ -0,0 +1,192 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2024
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+use crate::BitType;
+use hashbrown::HashMap;
+use pyo3::exceptions::{PyRuntimeError, PyValueError};
+use pyo3::prelude::*;
+use pyo3::types::PyList;
+use std::fmt::Debug;
+use std::hash::{Hash, Hasher};
+
+/// Private wrapper for Python-side Bit instances that implements
+/// [Hash] and [Eq], allowing them to be used in Rust hash-based
+/// sets and maps.
+///
+/// Python's `hash()` is called on the wrapped Bit instance during
+/// construction and returned from Rust's [Hash] trait impl.
+/// The impl of [PartialEq] first compares the native Py pointers
+/// to determine equality. If these are not equal, only then does
+/// it call `repr()` on both sides, which has a significant
+/// performance advantage.
+#[derive(Clone, Debug)]
+struct BitAsKey {
+    /// Python's `hash()` of the wrapped instance.
+    hash: isize,
+    /// The wrapped instance.
+    bit: PyObject,
+}
+
+impl BitAsKey {
+    pub fn new(bit: &Bound) -> Self {
+        BitAsKey {
+            // This really shouldn't fail, but if it does,
+            // we'll just use 0.
+            hash: bit.hash().unwrap_or(0),
+            bit: bit.clone().unbind(),
+        }
+    }
+}
+
+impl Hash for BitAsKey {
+    fn hash(&self, state: &mut H) {
+        state.write_isize(self.hash);
+    }
+}
+
+impl PartialEq for BitAsKey {
+    fn eq(&self, other: &Self) -> bool {
+        self.bit.is(&other.bit)
+            || Python::with_gil(|py| {
+                self.bit
+                    .bind(py)
+                    .repr()
+                    .unwrap()
+                    .eq(other.bit.bind(py).repr().unwrap())
+                    .unwrap()
+            })
+    }
+}
+
+impl Eq for BitAsKey {}
+
+#[derive(Clone, Debug)]
+pub(crate) struct BitData {
+    /// The public field name (i.e. `qubits` or `clbits`).
+    description: String,
+    /// Registered Python bits.
+    bits: Vec,
+    /// Maps Python bits to native type.
+    indices: HashMap,
+    /// The bits registered, cached as a PyList.
+    cached: Py,
+}
+
+pub(crate) struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>);
+
+impl BitData
+where
+    T: From + Copy,
+    BitType: From,
+{
+    pub fn new(py: Python<'_>, description: String) -> Self {
+        BitData {
+            description,
+            bits: Vec::new(),
+            indices: HashMap::new(),
+            cached: PyList::empty_bound(py).unbind(),
+        }
+    }
+
+    /// Gets the number of bits.
+    pub fn len(&self) -> usize {
+        self.bits.len()
+    }
+
+    /// Gets a reference to the underlying vector of Python bits.
+    #[inline]
+    pub fn bits(&self) -> &Vec {
+        &self.bits
+    }
+
+    /// Gets a reference to the cached Python list, maintained by
+    /// this instance.
+    #[inline]
+    pub fn cached(&self) -> &Py {
+        &self.cached
+    }
+
+    /// Finds the native bit index of the given Python bit.
+    #[inline]
+    pub fn find(&self, bit: &Bound) -> Option {
+        self.indices.get(&BitAsKey::new(bit)).copied()
+    }
+
+    /// Map the provided Python bits to their native indices.
+    /// An error is returned if any bit is not registered.
+    pub fn map_bits<'py>(
+        &self,
+        bits: impl IntoIterator>,
+    ) -> Result, BitNotFoundError<'py>> {
+        let v: Result, _> = bits
+            .into_iter()
+            .map(|b| {
+                self.indices
+                    .get(&BitAsKey::new(&b))
+                    .copied()
+                    .ok_or_else(|| BitNotFoundError(b))
+            })
+            .collect();
+        v.map(|x| x.into_iter())
+    }
+
+    /// Map the provided native indices to the corresponding Python
+    /// bit instances.
+    /// Panics if any of the indices are out of range.
+    pub fn map_indices(&self, bits: &[T]) -> impl Iterator> + ExactSizeIterator {
+        let v: Vec<_> = bits.iter().map(|i| self.get(*i).unwrap()).collect();
+        v.into_iter()
+    }
+
+    /// Gets the Python bit corresponding to the given native
+    /// bit index.
+    #[inline]
+    pub fn get(&self, index: T) -> Option<&PyObject> {
+        self.bits.get(>::from(index) as usize)
+    }
+
+    /// Adds a new Python bit.
+    pub fn add(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> {
+        if self.bits.len() != self.cached.bind(bit.py()).len() {
+            return Err(PyRuntimeError::new_err(
+            format!("This circuit's {} list has become out of sync with the circuit data. Did something modify it?", self.description)
+            ));
+        }
+        let idx: BitType = self.bits.len().try_into().map_err(|_| {
+            PyRuntimeError::new_err(format!(
+                "The number of {} in the circuit has exceeded the maximum capacity",
+                self.description
+            ))
+        })?;
+        if self
+            .indices
+            .try_insert(BitAsKey::new(bit), idx.into())
+            .is_ok()
+        {
+            self.bits.push(bit.into_py(py));
+            self.cached.bind(py).append(bit)?;
+        } else if strict {
+            return Err(PyValueError::new_err(format!(
+                "Existing bit {:?} cannot be re-added in strict mode.",
+                bit
+            )));
+        }
+        Ok(())
+    }
+
+    /// Called during Python garbage collection, only!.
+    /// Note: INVALIDATES THIS INSTANCE.
+    pub fn dispose(&mut self) {
+        self.indices.clear();
+        self.bits.clear();
+    }
+}
diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs
index d8eca9ef8546..fbb7c06fc89e 100644
--- a/crates/circuit/src/circuit_data.rs
+++ b/crates/circuit/src/circuit_data.rs
@@ -1,6 +1,6 @@
 // This code is part of Qiskit.
 //
-// (C) Copyright IBM 2023
+// (C) Copyright IBM 2023, 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
@@ -10,76 +10,17 @@
 // copyright notice, and modified files need to carry a notice indicating
 // that they have been altered from the originals.
 
+use crate::bit_data::{BitData, BitNotFoundError};
 use crate::circuit_instruction::CircuitInstruction;
-use crate::intern_context::{BitType, IndexType, InternContext};
-use crate::SliceOrInt;
+use crate::interner::{CacheFullError, IndexedInterner, Interner, InternerKey};
+use crate::packed_instruction::PackedInstruction;
+use crate::{Clbit, Qubit, SliceOrInt};
 
-use hashbrown::HashMap;
 use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError};
 use pyo3::prelude::*;
 use pyo3::types::{PyList, PySet, PySlice, PyTuple, PyType};
 use pyo3::{PyObject, PyResult, PyTraverseError, PyVisit};
-use std::hash::{Hash, Hasher};
-
-/// Private type used to store instructions with interned arg lists.
-#[derive(Clone, Debug)]
-struct PackedInstruction {
-    /// The Python-side operation instance.
-    op: PyObject,
-    /// The index under which the interner has stored `qubits`.
-    qubits_id: IndexType,
-    /// The index under which the interner has stored `clbits`.
-    clbits_id: IndexType,
-}
-
-/// Private wrapper for Python-side Bit instances that implements
-/// [Hash] and [Eq], allowing them to be used in Rust hash-based
-/// sets and maps.
-///
-/// Python's `hash()` is called on the wrapped Bit instance during
-/// construction and returned from Rust's [Hash] trait impl.
-/// The impl of [PartialEq] first compares the native Py pointers
-/// to determine equality. If these are not equal, only then does
-/// it call `repr()` on both sides, which has a significant
-/// performance advantage.
-#[derive(Clone, Debug)]
-struct BitAsKey {
-    /// Python's `hash()` of the wrapped instance.
-    hash: isize,
-    /// The wrapped instance.
-    bit: PyObject,
-}
-
-impl BitAsKey {
-    fn new(bit: &Bound) -> PyResult {
-        Ok(BitAsKey {
-            hash: bit.hash()?,
-            bit: bit.into_py(bit.py()),
-        })
-    }
-}
-
-impl Hash for BitAsKey {
-    fn hash(&self, state: &mut H) {
-        state.write_isize(self.hash);
-    }
-}
-
-impl PartialEq for BitAsKey {
-    fn eq(&self, other: &Self) -> bool {
-        self.bit.is(&other.bit)
-            || Python::with_gil(|py| {
-                self.bit
-                    .bind(py)
-                    .repr()
-                    .unwrap()
-                    .eq(other.bit.bind(py).repr().unwrap())
-                    .unwrap()
-            })
-    }
-}
-
-impl Eq for BitAsKey {}
+use std::mem;
 
 /// A container for :class:`.QuantumCircuit` instruction listings that stores
 /// :class:`.CircuitInstruction` instances in a packed form by interning
@@ -136,22 +77,29 @@ impl Eq for BitAsKey {}
 pub struct CircuitData {
     /// The packed instruction listing.
     data: Vec,
-    /// The intern context used to intern instruction bits.
-    intern_context: InternContext,
-    /// The qubits registered (e.g. through :meth:`~.CircuitData.add_qubit`).
-    qubits_native: Vec,
-    /// The clbits registered (e.g. through :meth:`~.CircuitData.add_clbit`).
-    clbits_native: Vec,
-    /// Map of :class:`.Qubit` instances to their index in
-    /// :attr:`.CircuitData.qubits`.
-    qubit_indices_native: HashMap,
-    /// Map of :class:`.Clbit` instances to their index in
-    /// :attr:`.CircuitData.clbits`.
-    clbit_indices_native: HashMap,
-    /// The qubits registered, cached as a ``list[Qubit]``.
-    qubits: Py,
-    /// The clbits registered, cached as a ``list[Clbit]``.
-    clbits: Py,
+    /// The cache used to intern instruction bits.
+    qargs_interner: IndexedInterner>,
+    /// The cache used to intern instruction bits.
+    cargs_interner: IndexedInterner>,
+    /// Qubits registered in the circuit.
+    qubits: BitData,
+    /// Clbits registered in the circuit.
+    clbits: BitData,
+}
+
+impl<'py> From> for PyErr {
+    fn from(error: BitNotFoundError) -> Self {
+        PyKeyError::new_err(format!(
+            "Bit {:?} has not been added to this circuit.",
+            error.0
+        ))
+    }
+}
+
+impl From for PyErr {
+    fn from(_: CacheFullError) -> Self {
+        PyRuntimeError::new_err("The bit operands cache is full!")
+    }
 }
 
 #[pymethods]
@@ -167,13 +115,10 @@ impl CircuitData {
     ) -> PyResult {
         let mut self_ = CircuitData {
             data: Vec::new(),
-            intern_context: InternContext::new(),
-            qubits_native: Vec::new(),
-            clbits_native: Vec::new(),
-            qubit_indices_native: HashMap::new(),
-            clbit_indices_native: HashMap::new(),
-            qubits: PyList::empty_bound(py).unbind(),
-            clbits: PyList::empty_bound(py).unbind(),
+            qargs_interner: IndexedInterner::new(),
+            cargs_interner: IndexedInterner::new(),
+            qubits: BitData::new(py, "qubits".to_string()),
+            clbits: BitData::new(py, "clbits".to_string()),
         };
         if let Some(qubits) = qubits {
             for bit in qubits.iter()? {
@@ -197,8 +142,8 @@ impl CircuitData {
         let args = {
             let self_ = self_.borrow();
             (
-                self_.qubits.clone_ref(py),
-                self_.clbits.clone_ref(py),
+                self_.qubits.cached().clone_ref(py),
+                self_.clbits.cached().clone_ref(py),
                 None::<()>,
                 self_.data.len(),
             )
@@ -217,7 +162,7 @@ impl CircuitData {
     ///     list(:class:`.Qubit`): The current sequence of registered qubits.
     #[getter]
     pub fn qubits(&self, py: Python<'_>) -> Py {
-        self.qubits.clone_ref(py)
+        self.qubits.cached().clone_ref(py)
     }
 
     /// Return the number of qubits. This is equivalent to the length of the list returned by
@@ -227,7 +172,7 @@ impl CircuitData {
     ///     int: The number of qubits.
     #[getter]
     pub fn num_qubits(&self) -> usize {
-        self.qubits_native.len()
+        self.qubits.len()
     }
 
     /// Returns the current sequence of registered :class:`.Clbit`
@@ -242,7 +187,7 @@ impl CircuitData {
     ///     list(:class:`.Clbit`): The current sequence of registered clbits.
     #[getter]
     pub fn clbits(&self, py: Python<'_>) -> Py {
-        self.clbits.clone_ref(py)
+        self.clbits.cached().clone_ref(py)
     }
 
     /// Return the number of clbits. This is equivalent to the length of the list returned by
@@ -252,7 +197,7 @@ impl CircuitData {
     ///     int: The number of clbits.
     #[getter]
     pub fn num_clbits(&self) -> usize {
-        self.clbits_native.len()
+        self.clbits.len()
     }
 
     /// Return the width of the circuit. This is the number of qubits plus the
@@ -275,31 +220,7 @@ impl CircuitData {
     ///         was provided.
     #[pyo3(signature = (bit, *, strict=true))]
     pub fn add_qubit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> {
-        if self.num_qubits() != self.qubits.bind(bit.py()).len() {
-            return Err(PyRuntimeError::new_err(concat!(
-                "This circuit's 'qubits' list has become out of sync with the circuit data.",
-                " Did something modify it?"
-            )));
-        }
-        let idx: BitType = self.num_qubits().try_into().map_err(|_| {
-            PyRuntimeError::new_err(
-                "The number of qubits in the circuit has exceeded the maximum capacity",
-            )
-        })?;
-        if self
-            .qubit_indices_native
-            .try_insert(BitAsKey::new(bit)?, idx)
-            .is_ok()
-        {
-            self.qubits_native.push(bit.into_py(py));
-            self.qubits.bind(py).append(bit)?;
-        } else if strict {
-            return Err(PyValueError::new_err(format!(
-                "Existing bit {:?} cannot be re-added in strict mode.",
-                bit
-            )));
-        }
-        Ok(())
+        self.qubits.add(py, bit, strict)
     }
 
     /// Registers a :class:`.Clbit` instance.
@@ -313,31 +234,7 @@ impl CircuitData {
     ///         was provided.
     #[pyo3(signature = (bit, *, strict=true))]
     pub fn add_clbit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> {
-        if self.num_clbits() != self.clbits.bind(bit.py()).len() {
-            return Err(PyRuntimeError::new_err(concat!(
-                "This circuit's 'clbits' list has become out of sync with the circuit data.",
-                " Did something modify it?"
-            )));
-        }
-        let idx: BitType = self.num_clbits().try_into().map_err(|_| {
-            PyRuntimeError::new_err(
-                "The number of clbits in the circuit has exceeded the maximum capacity",
-            )
-        })?;
-        if self
-            .clbit_indices_native
-            .try_insert(BitAsKey::new(bit)?, idx)
-            .is_ok()
-        {
-            self.clbits_native.push(bit.into_py(py));
-            self.clbits.bind(py).append(bit)?;
-        } else if strict {
-            return Err(PyValueError::new_err(format!(
-                "Existing bit {:?} cannot be re-added in strict mode.",
-                bit
-            )));
-        }
-        Ok(())
+        self.clbits.add(py, bit, strict)
     }
 
     /// Performs a shallow copy.
@@ -347,12 +244,13 @@ impl CircuitData {
     pub fn copy(&self, py: Python<'_>) -> PyResult {
         let mut res = CircuitData::new(
             py,
-            Some(self.qubits.bind(py)),
-            Some(self.clbits.bind(py)),
+            Some(self.qubits.cached().bind(py)),
+            Some(self.clbits.cached().bind(py)),
             None,
             0,
         )?;
-        res.intern_context = self.intern_context.clone();
+        res.qargs_interner = self.qargs_interner.clone();
+        res.cargs_interner = self.cargs_interner.clone();
         res.data.clone_from(&self.data);
         Ok(res)
     }
@@ -376,11 +274,11 @@ impl CircuitData {
         let qubits = PySet::empty_bound(py)?;
         let clbits = PySet::empty_bound(py)?;
         for inst in self.data.iter() {
-            for b in self.intern_context.lookup(inst.qubits_id).iter() {
-                qubits.add(self.qubits_native[*b as usize].clone_ref(py))?;
+            for b in self.qargs_interner.intern(inst.qubits_id).value.iter() {
+                qubits.add(self.qubits.get(*b).unwrap().clone_ref(py))?;
             }
-            for b in self.intern_context.lookup(inst.clbits_id).iter() {
-                clbits.add(self.clbits_native[*b as usize].clone_ref(py))?;
+            for b in self.cargs_interner.intern(inst.clbits_id).value.iter() {
+                clbits.add(self.clbits.get(*b).unwrap().clone_ref(py))?;
             }
         }
 
@@ -496,12 +394,7 @@ impl CircuitData {
                     self.num_qubits(),
                 )));
             }
-            std::mem::swap(&mut temp.qubits, &mut self.qubits);
-            std::mem::swap(&mut temp.qubits_native, &mut self.qubits_native);
-            std::mem::swap(
-                &mut temp.qubit_indices_native,
-                &mut self.qubit_indices_native,
-            );
+            mem::swap(&mut temp.qubits, &mut self.qubits);
         }
         if clbits.is_some() {
             if temp.num_clbits() < self.num_clbits() {
@@ -511,12 +404,7 @@ impl CircuitData {
                     self.num_clbits(),
                 )));
             }
-            std::mem::swap(&mut temp.clbits, &mut self.clbits);
-            std::mem::swap(&mut temp.clbits_native, &mut self.clbits_native);
-            std::mem::swap(
-                &mut temp.clbit_indices_native,
-                &mut self.clbit_indices_native,
-            );
+            mem::swap(&mut temp.clbits, &mut self.clbits);
         }
         Ok(())
     }
@@ -536,7 +424,17 @@ impl CircuitData {
         ) -> PyResult> {
             let index = self_.convert_py_index(index)?;
             if let Some(inst) = self_.data.get(index) {
-                self_.unpack(py, inst)
+                let qubits = self_.qargs_interner.intern(inst.qubits_id);
+                let clbits = self_.cargs_interner.intern(inst.clbits_id);
+                Py::new(
+                    py,
+                    CircuitInstruction::new(
+                        py,
+                        inst.op.clone_ref(py),
+                        self_.qubits.map_indices(qubits.value),
+                        self_.clbits.map_indices(clbits.value),
+                    ),
+                )
             } else {
                 Err(PyIndexError::new_err(format!(
                     "No element at index {:?} in circuit data",
@@ -637,7 +535,7 @@ impl CircuitData {
                 let index = self.convert_py_index(index)?;
                 let value: PyRef = value.extract()?;
                 let mut packed = self.pack(py, value)?;
-                std::mem::swap(&mut packed, &mut self.data[index]);
+                mem::swap(&mut packed, &mut self.data[index]);
                 Ok(())
             }
         }
@@ -676,28 +574,38 @@ impl CircuitData {
             self.data.reserve(other.data.len());
             for inst in other.data.iter() {
                 let qubits = other
-                    .intern_context
-                    .lookup(inst.qubits_id)
+                    .qargs_interner
+                    .intern(inst.qubits_id)
+                    .value
                     .iter()
                     .map(|b| {
-                        Ok(self.qubit_indices_native
-                            [&BitAsKey::new(other.qubits_native[*b as usize].bind(py))?])
+                        Ok(self
+                            .qubits
+                            .find(other.qubits.get(*b).unwrap().bind(py))
+                            .unwrap())
                     })
-                    .collect::>>()?;
+                    .collect::>>()?;
                 let clbits = other
-                    .intern_context
-                    .lookup(inst.clbits_id)
+                    .cargs_interner
+                    .intern(inst.clbits_id)
+                    .value
                     .iter()
                     .map(|b| {
-                        Ok(self.clbit_indices_native
-                            [&BitAsKey::new(other.clbits_native[*b as usize].bind(py))?])
+                        Ok(self
+                            .clbits
+                            .find(other.clbits.get(*b).unwrap().bind(py))
+                            .unwrap())
                     })
-                    .collect::>>()?;
+                    .collect::>>()?;
 
+                let qubits_id =
+                    Interner::intern(&mut self.qargs_interner, InternerKey::Value(qubits))?;
+                let clbits_id =
+                    Interner::intern(&mut self.cargs_interner, InternerKey::Value(clbits))?;
                 self.data.push(PackedInstruction {
                     op: inst.op.clone_ref(py),
-                    qubits_id: self.intern_context.intern(qubits)?,
-                    clbits_id: self.intern_context.intern(clbits)?,
+                    qubits_id: qubits_id.index,
+                    clbits_id: clbits_id.index,
                 });
             }
             return Ok(());
@@ -751,7 +659,7 @@ impl CircuitData {
         for packed in self.data.iter() {
             visit.call(&packed.op)?;
         }
-        for bit in self.qubits_native.iter().chain(self.clbits_native.iter()) {
+        for bit in self.qubits.bits().iter().chain(self.clbits.bits().iter()) {
             visit.call(bit)?;
         }
 
@@ -759,18 +667,16 @@ impl CircuitData {
         //   There's no need to visit the native Rust data
         //   structures used for internal tracking: the only Python
         //   references they contain are to the bits in these lists!
-        visit.call(&self.qubits)?;
-        visit.call(&self.clbits)?;
+        visit.call(self.qubits.cached())?;
+        visit.call(self.clbits.cached())?;
         Ok(())
     }
 
     fn __clear__(&mut self) {
         // Clear anything that could have a reference cycle.
         self.data.clear();
-        self.qubits_native.clear();
-        self.clbits_native.clear();
-        self.qubit_indices_native.clear();
-        self.clbit_indices_native.clear();
+        self.qubits.dispose();
+        self.clbits.dispose();
     }
 }
 
@@ -824,61 +730,23 @@ impl CircuitData {
         Ok(index as usize)
     }
 
-    /// Returns a [PackedInstruction] containing the original operation
-    /// of `elem` and [InternContext] indices of its `qubits` and `clbits`
-    /// fields.
     fn pack(
         &mut self,
-        py: Python<'_>,
-        inst: PyRef,
+        py: Python,
+        value: PyRef,
     ) -> PyResult {
-        let mut interned_bits =
-            |indices: &HashMap, bits: &Bound| -> PyResult {
-                let args = bits
-                    .into_iter()
-                    .map(|b| {
-                        let key = BitAsKey::new(&b)?;
-                        indices.get(&key).copied().ok_or_else(|| {
-                            PyKeyError::new_err(format!(
-                                "Bit {:?} has not been added to this circuit.",
-                                b
-                            ))
-                        })
-                    })
-                    .collect::>>()?;
-                self.intern_context.intern(args)
-            };
+        let qubits = Interner::intern(
+            &mut self.qargs_interner,
+            InternerKey::Value(self.qubits.map_bits(value.qubits.bind(py))?.collect()),
+        )?;
+        let clbits = Interner::intern(
+            &mut self.cargs_interner,
+            InternerKey::Value(self.clbits.map_bits(value.clbits.bind(py))?.collect()),
+        )?;
         Ok(PackedInstruction {
-            op: inst.operation.clone_ref(py),
-            qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?,
-            clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?,
+            op: value.operation.clone_ref(py),
+            qubits_id: qubits.index,
+            clbits_id: clbits.index,
         })
     }
-
-    fn unpack(&self, py: Python<'_>, inst: &PackedInstruction) -> PyResult> {
-        Py::new(
-            py,
-            CircuitInstruction {
-                operation: inst.op.clone_ref(py),
-                qubits: PyTuple::new_bound(
-                    py,
-                    self.intern_context
-                        .lookup(inst.qubits_id)
-                        .iter()
-                        .map(|i| self.qubits_native[*i as usize].clone_ref(py))
-                        .collect::>(),
-                )
-                .unbind(),
-                clbits: PyTuple::new_bound(
-                    py,
-                    self.intern_context
-                        .lookup(inst.clbits_id)
-                        .iter()
-                        .map(|i| self.clbits_native[*i as usize].clone_ref(py))
-                        .collect::>(),
-                )
-                .unbind(),
-            },
-        )
-    }
 }
diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs
index 86bd2e69c111..ac61ae81a619 100644
--- a/crates/circuit/src/circuit_instruction.rs
+++ b/crates/circuit/src/circuit_instruction.rs
@@ -63,15 +63,36 @@ pub struct CircuitInstruction {
     pub clbits: Py,
 }
 
+impl CircuitInstruction {
+    pub fn new(
+        py: Python,
+        operation: PyObject,
+        qubits: impl IntoIterator,
+        clbits: impl IntoIterator,
+    ) -> Self
+    where
+        T1: ToPyObject,
+        T2: ToPyObject,
+        U1: ExactSizeIterator,
+        U2: ExactSizeIterator,
+    {
+        CircuitInstruction {
+            operation,
+            qubits: PyTuple::new_bound(py, qubits).unbind(),
+            clbits: PyTuple::new_bound(py, clbits).unbind(),
+        }
+    }
+}
+
 #[pymethods]
 impl CircuitInstruction {
     #[new]
-    pub fn new(
+    pub fn py_new(
         py: Python<'_>,
         operation: PyObject,
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
-    ) -> PyResult {
+    ) -> PyResult> {
         fn as_tuple(py: Python<'_>, seq: Option<&Bound>) -> PyResult> {
             match seq {
                 None => Ok(PyTuple::empty_bound(py).unbind()),
@@ -95,11 +116,14 @@ impl CircuitInstruction {
             }
         }
 
-        Ok(CircuitInstruction {
-            operation,
-            qubits: as_tuple(py, qubits)?,
-            clbits: as_tuple(py, clbits)?,
-        })
+        Py::new(
+            py,
+            CircuitInstruction {
+                operation,
+                qubits: as_tuple(py, qubits)?,
+                clbits: as_tuple(py, clbits)?,
+            },
+        )
     }
 
     /// Returns a shallow copy.
@@ -120,8 +144,8 @@ impl CircuitInstruction {
         operation: Option,
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
-    ) -> PyResult {
-        CircuitInstruction::new(
+    ) -> PyResult> {
+        CircuitInstruction::py_new(
             py,
             operation.unwrap_or_else(|| self.operation.clone_ref(py)),
             Some(qubits.unwrap_or_else(|| self.qubits.bind(py))),
diff --git a/crates/circuit/src/intern_context.rs b/crates/circuit/src/intern_context.rs
deleted file mode 100644
index 0c8b596e6dd8..000000000000
--- a/crates/circuit/src/intern_context.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-// This code is part of Qiskit.
-//
-// (C) Copyright IBM 2023
-//
-// This code is licensed under the Apache License, Version 2.0. You may
-// obtain a copy of this license in the LICENSE.txt file in the root directory
-// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-//
-// Any modifications or derivative works of this code must retain this
-// copyright notice, and modified files need to carry a notice indicating
-// that they have been altered from the originals.
-
-use hashbrown::HashMap;
-use pyo3::exceptions::PyRuntimeError;
-use pyo3::PyResult;
-use std::sync::Arc;
-
-pub type IndexType = u32;
-pub type BitType = u32;
-
-/// A Rust-only data structure (not a pyclass!) for interning
-/// `Vec`.
-///
-/// Takes ownership of vectors given to [InternContext.intern]
-/// and returns an [IndexType] index that can be used to look up
-/// an _equivalent_ sequence by reference via [InternContext.lookup].
-#[derive(Clone, Debug)]
-pub struct InternContext {
-    slots: Vec>>,
-    slot_lookup: HashMap>, IndexType>,
-}
-
-impl InternContext {
-    pub fn new() -> Self {
-        InternContext {
-            slots: Vec::new(),
-            slot_lookup: HashMap::new(),
-        }
-    }
-
-    /// Takes `args` by reference and returns an index that can be used
-    /// to obtain a reference to an equivalent sequence of `BitType` by
-    /// calling [CircuitData.lookup].
-    pub fn intern(&mut self, args: Vec) -> PyResult {
-        if let Some(slot_idx) = self.slot_lookup.get(&args) {
-            return Ok(*slot_idx);
-        }
-
-        let args = Arc::new(args);
-        let slot_idx: IndexType = self
-            .slots
-            .len()
-            .try_into()
-            .map_err(|_| PyRuntimeError::new_err("InternContext capacity exceeded!"))?;
-        self.slots.push(args.clone());
-        self.slot_lookup.insert_unique_unchecked(args, slot_idx);
-        Ok(slot_idx)
-    }
-
-    /// Returns the sequence corresponding to `slot_idx`, which must
-    /// be a value returned by [InternContext.intern].
-    pub fn lookup(&self, slot_idx: IndexType) -> &[BitType] {
-        self.slots.get(slot_idx as usize).unwrap()
-    }
-}
-
-impl Default for InternContext {
-    fn default() -> Self {
-        Self::new()
-    }
-}
diff --git a/crates/circuit/src/interner.rs b/crates/circuit/src/interner.rs
new file mode 100644
index 000000000000..426675702053
--- /dev/null
+++ b/crates/circuit/src/interner.rs
@@ -0,0 +1,125 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2023, 2024
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+use hashbrown::HashMap;
+use pyo3::{IntoPy, PyObject, Python};
+use std::hash::Hash;
+use std::sync::Arc;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Index(u32);
+
+pub enum InternerKey {
+    Index(Index),
+    Value(T),
+}
+
+impl From for InternerKey {
+    fn from(value: Index) -> Self {
+        InternerKey::Index(value)
+    }
+}
+
+pub struct InternerValue<'a, T> {
+    pub index: Index,
+    pub value: &'a T,
+}
+
+impl IntoPy for Index {
+    fn into_py(self, py: Python<'_>) -> PyObject {
+        self.0.into_py(py)
+    }
+}
+
+pub struct CacheFullError;
+
+/// An append-only data structure for interning generic
+/// Rust types.
+#[derive(Clone, Debug)]
+pub struct IndexedInterner {
+    entries: Vec>,
+    index_lookup: HashMap, Index>,
+}
+
+pub trait Interner {
+    type Key;
+    type Output;
+
+    /// Takes ownership of the provided key and returns the interned
+    /// type.
+    fn intern(self, value: Self::Key) -> Self::Output;
+}
+
+impl<'a, T> Interner for &'a IndexedInterner {
+    type Key = Index;
+    type Output = InternerValue<'a, T>;
+
+    fn intern(self, index: Index) -> Self::Output {
+        let value = self.entries.get(index.0 as usize).unwrap();
+        InternerValue {
+            index,
+            value: value.as_ref(),
+        }
+    }
+}
+
+impl<'a, T> Interner for &'a mut IndexedInterner
+where
+    T: Eq + Hash,
+{
+    type Key = InternerKey;
+    type Output = Result, CacheFullError>;
+
+    fn intern(self, key: Self::Key) -> Self::Output {
+        match key {
+            InternerKey::Index(index) => {
+                let value = self.entries.get(index.0 as usize).unwrap();
+                Ok(InternerValue {
+                    index,
+                    value: value.as_ref(),
+                })
+            }
+            InternerKey::Value(value) => {
+                if let Some(index) = self.index_lookup.get(&value).copied() {
+                    Ok(InternerValue {
+                        index,
+                        value: self.entries.get(index.0 as usize).unwrap(),
+                    })
+                } else {
+                    let args = Arc::new(value);
+                    let index: Index =
+                        Index(self.entries.len().try_into().map_err(|_| CacheFullError)?);
+                    self.entries.push(args.clone());
+                    Ok(InternerValue {
+                        index,
+                        value: self.index_lookup.insert_unique_unchecked(args, index).0,
+                    })
+                }
+            }
+        }
+    }
+}
+
+impl IndexedInterner {
+    pub fn new() -> Self {
+        IndexedInterner {
+            entries: Vec::new(),
+            index_lookup: HashMap::new(),
+        }
+    }
+}
+
+impl Default for IndexedInterner {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index c186c4243e93..90f2b7c7f070 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -1,6 +1,6 @@
 // This code is part of Qiskit.
 //
-// (C) Copyright IBM 2023
+// (C) Copyright IBM 2023, 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
@@ -13,7 +13,10 @@
 pub mod circuit_data;
 pub mod circuit_instruction;
 pub mod dag_node;
-pub mod intern_context;
+
+mod bit_data;
+mod interner;
+mod packed_instruction;
 
 use pyo3::prelude::*;
 use pyo3::types::PySlice;
@@ -28,6 +31,36 @@ pub enum SliceOrInt<'a> {
     Slice(Bound<'a, PySlice>),
 }
 
+pub type BitType = u32;
+#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
+pub struct Qubit(BitType);
+#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
+pub struct Clbit(BitType);
+
+impl From for Qubit {
+    fn from(value: BitType) -> Self {
+        Qubit(value)
+    }
+}
+
+impl From for BitType {
+    fn from(value: Qubit) -> Self {
+        value.0
+    }
+}
+
+impl From for Clbit {
+    fn from(value: BitType) -> Self {
+        Clbit(value)
+    }
+}
+
+impl From for BitType {
+    fn from(value: Clbit) -> Self {
+        value.0
+    }
+}
+
 #[pymodule]
 pub fn circuit(m: Bound) -> PyResult<()> {
     m.add_class::()?;
diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs
new file mode 100644
index 000000000000..0c793f2b6408
--- /dev/null
+++ b/crates/circuit/src/packed_instruction.rs
@@ -0,0 +1,25 @@
+// 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::interner::Index;
+use pyo3::prelude::*;
+
+/// Private type used to store instructions with interned arg lists.
+#[derive(Clone, Debug)]
+pub(crate) struct PackedInstruction {
+    /// The Python-side operation instance.
+    pub op: PyObject,
+    /// The index under which the interner has stored `qubits`.
+    pub qubits_id: Index,
+    /// The index under which the interner has stored `clbits`.
+    pub clbits_id: Index,
+}

From 92e78ef72a0f8aaaa57c4ca7f97e7646ace57c64 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 6 Jun 2024 21:53:46 +0100
Subject: [PATCH 128/179] Bump pylint to 3.2.2 (#12520)

* Bump pylint to 3.2.2

This upgrades pylint to a version compatible with Python 3.8 through
3.12, like Qiskit.  There are a couple of false positives (pylint is
incorrect about its `use-yield-from` in the cases it flags), and has
included a couple of new stylistic opinions that are not necessary to
enforce.

The `possibly-used-before-assignment` lint is quite possibly a good one,
but there are far too many instances in the Qiskit codebase right now to
fix, whereas our current version of pylint is preventing us from running
it with Python 3.12.

* Update requirements-dev.txt

Co-authored-by: Pierre Sassoulas 

* Remove suppressions fixed in pylint 3.2.3

---------

Co-authored-by: Pierre Sassoulas 
Co-authored-by: Matthew Treinish 
---
 pyproject.toml                       | 2 ++
 qiskit/transpiler/passmanager.py     | 4 ++--
 qiskit/visualization/circuit/text.py | 8 +-------
 requirements-dev.txt                 | 4 ++--
 4 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 35a14a5524ba..6e57fa53a7e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -213,6 +213,7 @@ disable = [
     "docstring-first-line-empty", # relax docstring style
     "import-outside-toplevel", "import-error", # overzealous with our optionals/dynamic packages
     "nested-min-max", # this gives false equivalencies if implemented for the current lint version
+    "consider-using-max-builtin", "consider-using-min-builtin",  # unnecessary stylistic opinion
 # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so,
 #  remove from here and fix the issues. Else, move it above this section and add a comment
 #  with the rationale
@@ -222,6 +223,7 @@ disable = [
     "no-member",
     "no-value-for-parameter",
     "not-context-manager",
+    "possibly-used-before-assignment",
     "unexpected-keyword-arg",
     "unnecessary-dunder-call",
     "unnecessary-lambda-assignment",
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index bb1344e34cba..96c0be11b448 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -18,7 +18,7 @@
 import re
 from collections.abc import Iterator, Iterable, Callable
 from functools import wraps
-from typing import Union, List, Any
+from typing import Union, List, Any, TypeVar
 
 from qiskit.circuit import QuantumCircuit
 from qiskit.converters import circuit_to_dag, dag_to_circuit
@@ -31,7 +31,7 @@
 from .exceptions import TranspilerError
 from .layout import TranspileLayout, Layout
 
-_CircuitsT = Union[List[QuantumCircuit], QuantumCircuit]
+_CircuitsT = TypeVar("_CircuitsT", bound=Union[List[QuantumCircuit], QuantumCircuit])
 
 
 class PassManager(BasePassManager):
diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py
index c2846da0b758..bec1ccf4a3ee 100644
--- a/qiskit/visualization/circuit/text.py
+++ b/qiskit/visualization/circuit/text.py
@@ -739,13 +739,7 @@ def __init__(
         self._wire_map = {}
         self.cregbundle = cregbundle
 
-        if encoding:
-            self.encoding = encoding
-        else:
-            if sys.stdout.encoding:
-                self.encoding = sys.stdout.encoding
-            else:
-                self.encoding = "utf8"
+        self.encoding = encoding or sys.stdout.encoding or "utf8"
 
         self._nest_depth = 0  # nesting depth for control flow ops
         self._expr_text = ""  # expression text to display
diff --git a/requirements-dev.txt b/requirements-dev.txt
index c75237e77edd..7c5a909bd395 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -17,8 +17,8 @@ black[jupyter]~=24.1
 #
 # These versions are pinned precisely because pylint frequently includes new
 # on-by-default lint failures in new versions, which breaks our CI.
-astroid==2.14.2
-pylint==2.16.2
+astroid==3.2.2
+pylint==3.2.3
 ruff==0.0.267
 
 

From 90f09dab70a310237374c95da840c15ba9ea13bb Mon Sep 17 00:00:00 2001
From: Catherine Lozano 
Date: Thu, 6 Jun 2024 19:28:33 -0400
Subject: [PATCH 129/179] depreciated U3 gate(qc.U3) changed to qc.U (#12514)

---
 test/benchmarks/random_circuit_hex.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/benchmarks/random_circuit_hex.py b/test/benchmarks/random_circuit_hex.py
index 952c651df9f1..92f1cb5843bf 100644
--- a/test/benchmarks/random_circuit_hex.py
+++ b/test/benchmarks/random_circuit_hex.py
@@ -41,7 +41,7 @@ def make_circuit_ring(nq, depth, seed):
         for i in range(nq):  # round of single-qubit unitaries
             u = random_unitary(2, seed).data
             angles = decomposer.angles(u)
-            qc.u3(angles[0], angles[1], angles[2], q[i])
+            qc.u(angles[0], angles[1], angles[2], q[i])
 
     # insert the final measurements
     qcm = copy.deepcopy(qc)

From 72f09adf7e434dbdfc84b6a2cf12af044f99cf8d Mon Sep 17 00:00:00 2001
From: Will Shanks 
Date: Thu, 6 Jun 2024 19:29:08 -0400
Subject: [PATCH 130/179] Avoid exception in `Target.has_calibration` for
 instruction without properties (#12526)

`Target.add_instruction` allows passing `None` in place of an
`InstructionProperties` instance. In this case, there will be no
`_calibration` attribute, so the `getattr` call properties needs to
provide a default value.
---
 qiskit/transpiler/target.py                   |  2 +-
 ...ration-no-properties-f3be18f2d58f330a.yaml |  7 ++++++
 test/python/transpiler/test_target.py         | 25 +++++++++++++++++++
 3 files changed, 33 insertions(+), 1 deletion(-)
 create mode 100644 releasenotes/notes/target-has-calibration-no-properties-f3be18f2d58f330a.yaml

diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py
index f7b5227c0266..53daa4ccbe65 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -892,7 +892,7 @@ def has_calibration(
             return False
         if qargs not in self._gate_map[operation_name]:
             return False
-        return getattr(self._gate_map[operation_name][qargs], "_calibration") is not None
+        return getattr(self._gate_map[operation_name][qargs], "_calibration", None) is not None
 
     def get_calibration(
         self,
diff --git a/releasenotes/notes/target-has-calibration-no-properties-f3be18f2d58f330a.yaml b/releasenotes/notes/target-has-calibration-no-properties-f3be18f2d58f330a.yaml
new file mode 100644
index 000000000000..07970679722d
--- /dev/null
+++ b/releasenotes/notes/target-has-calibration-no-properties-f3be18f2d58f330a.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    :meth:`.Target.has_calibration` has been updated so that it does not raise
+    an exception for an instruction that has been added to the target with
+    ``None`` for its instruction properties. Fixes
+    `#12525 `__.
diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py
index 646b29dd3832..f63ed5061cc7 100644
--- a/test/python/transpiler/test_target.py
+++ b/test/python/transpiler/test_target.py
@@ -1366,6 +1366,31 @@ def test_get_empty_target_calibration(self):
 
         self.assertIsNone(target["x"][(0,)].calibration)
 
+    def test_has_calibration(self):
+        target = Target()
+        properties = {
+            (0,): InstructionProperties(duration=100, error=0.1),
+            (1,): None,
+        }
+        target.add_instruction(XGate(), properties)
+
+        # Test false for properties with no calibration
+        self.assertFalse(target.has_calibration("x", (0,)))
+        # Test false for no properties
+        self.assertFalse(target.has_calibration("x", (1,)))
+
+        properties = {
+            (0,): InstructionProperties(
+                duration=self.custom_sx_q0.duration,
+                error=None,
+                calibration=self.custom_sx_q0,
+            )
+        }
+        target.add_instruction(SXGate(), properties)
+
+        # Test true for properties with calibration
+        self.assertTrue(target.has_calibration("sx", (0,)))
+
     def test_loading_legacy_ugate_instmap(self):
         # This is typical IBM backend situation.
         # IBM provider used to have u1, u2, u3 in the basis gates and

From d18a74cd45922f4960a9eb90ceef89f9c6c184e5 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Fri, 7 Jun 2024 12:49:18 +0100
Subject: [PATCH 131/179] Fix `QuantumCircuit.depth` with zero-operands and
 `Expr` nodes (#12429)

This causes `QuantumCircuit.depth` to correctly handle cases where a
circuit instruction has zero operands (such as `GlobalPhaseGate`), and
to treat classical bits and real-time variables used inside `Expr`
conditions as part of the depth calculations.  This is in line with
`DAGCircuit`.

This commit still does not add the same `recurse` argument from
`DAGCircuit.depth`, because the arguments for not adding it to
`QuantumCircuit.depth` at the time still hold; there is no clear meaning
to it for general control flow from a user's perspective, and it was
only added to the `DAGCircuit` methods because there it is more of a
proxy for optimising over all possible inner blocks.
---
 qiskit/circuit/quantumcircuit.py              | 82 ++++++++-----------
 .../fix-qc-depth-0q-cdcc9aa14e237e68.yaml     |  8 ++
 .../python/circuit/test_circuit_properties.py | 55 ++++++++++++-
 3 files changed, 95 insertions(+), 50 deletions(-)
 create mode 100644 releasenotes/notes/fix-qc-depth-0q-cdcc9aa14e237e68.yaml

diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 73c5101bb469..ea4361fd8255 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -45,7 +45,7 @@
 from qiskit.circuit.exceptions import CircuitError
 from . import _classical_resource_map
 from ._utils import sort_parameters
-from .controlflow import ControlFlowOp
+from .controlflow import ControlFlowOp, _builder_utils
 from .controlflow.builder import CircuitScopeInterface, ControlFlowBuilderBlock
 from .controlflow.break_loop import BreakLoopOp, BreakLoopPlaceholder
 from .controlflow.continue_loop import ContinueLoopOp, ContinueLoopPlaceholder
@@ -3307,6 +3307,9 @@ def depth(
     ) -> int:
         """Return circuit depth (i.e., length of critical path).
 
+        .. warning::
+            This operation is not well defined if the circuit contains control-flow operations.
+
         Args:
             filter_function: A function to decide which instructions count to increase depth.
                 Should take as a single positional input a :class:`CircuitInstruction`.
@@ -3332,59 +3335,40 @@ def depth(
 
                 assert qc.depth(lambda instr: len(instr.qubits) > 1) == 1
         """
-        # Assign each bit in the circuit a unique integer
-        # to index into op_stack.
-        bit_indices: dict[Qubit | Clbit, int] = {
-            bit: idx for idx, bit in enumerate(self.qubits + self.clbits)
+        obj_depths = {
+            obj: 0 for objects in (self.qubits, self.clbits, self.iter_vars()) for obj in objects
         }
 
-        # If no bits, return 0
-        if not bit_indices:
-            return 0
+        def update_from_expr(objects, node):
+            for var in expr.iter_vars(node):
+                if var.standalone:
+                    objects.add(var)
+                else:
+                    objects.update(_builder_utils.node_resources(var).clbits)
 
-        # A list that holds the height of each qubit
-        # and classical bit.
-        op_stack = [0] * len(bit_indices)
-
-        # Here we are playing a modified version of
-        # Tetris where we stack gates, but multi-qubit
-        # gates, or measurements have a block for each
-        # qubit or cbit that are connected by a virtual
-        # line so that they all stacked at the same depth.
-        # Conditional gates act on all cbits in the register
-        # they are conditioned on.
-        # The max stack height is the circuit depth.
         for instruction in self._data:
-            levels = []
-            reg_ints = []
-            for ind, reg in enumerate(instruction.qubits + instruction.clbits):
-                # Add to the stacks of the qubits and
-                # cbits used in the gate.
-                reg_ints.append(bit_indices[reg])
-                if filter_function(instruction):
-                    levels.append(op_stack[reg_ints[ind]] + 1)
-                else:
-                    levels.append(op_stack[reg_ints[ind]])
-            # Assuming here that there is no conditional
-            # snapshots or barriers ever.
-            if getattr(instruction.operation, "condition", None):
-                # Controls operate over all bits of a classical register
-                # or over a single bit
-                if isinstance(instruction.operation.condition[0], Clbit):
-                    condition_bits = [instruction.operation.condition[0]]
+            objects = set(itertools.chain(instruction.qubits, instruction.clbits))
+            if (condition := getattr(instruction.operation, "condition", None)) is not None:
+                objects.update(_builder_utils.condition_resources(condition).clbits)
+                if isinstance(condition, expr.Expr):
+                    update_from_expr(objects, condition)
                 else:
-                    condition_bits = instruction.operation.condition[0]
-                for cbit in condition_bits:
-                    idx = bit_indices[cbit]
-                    if idx not in reg_ints:
-                        reg_ints.append(idx)
-                        levels.append(op_stack[idx] + 1)
-
-            max_level = max(levels)
-            for ind in reg_ints:
-                op_stack[ind] = max_level
-
-        return max(op_stack)
+                    objects.update(_builder_utils.condition_resources(condition).clbits)
+            elif isinstance(instruction.operation, SwitchCaseOp):
+                update_from_expr(objects, expr.lift(instruction.operation.target))
+            elif isinstance(instruction.operation, Store):
+                update_from_expr(objects, instruction.operation.lvalue)
+                update_from_expr(objects, instruction.operation.rvalue)
+
+            # If we're counting this as adding to depth, do so.  If not, it still functions as a
+            # data synchronisation point between the objects (think "barrier"), so the depths still
+            # get updated to match the current max over the affected objects.
+            new_depth = max((obj_depths[obj] for obj in objects), default=0)
+            if filter_function(instruction):
+                new_depth += 1
+            for obj in objects:
+                obj_depths[obj] = new_depth
+        return max(obj_depths.values(), default=0)
 
     def width(self) -> int:
         """Return number of qubits plus clbits in circuit.
diff --git a/releasenotes/notes/fix-qc-depth-0q-cdcc9aa14e237e68.yaml b/releasenotes/notes/fix-qc-depth-0q-cdcc9aa14e237e68.yaml
new file mode 100644
index 000000000000..a0744b3dd89a
--- /dev/null
+++ b/releasenotes/notes/fix-qc-depth-0q-cdcc9aa14e237e68.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+  - |
+    :meth:`.QuantumCircuit.depth` will now correctly handle operations that
+    do not have operands, such as :class:`.GlobalPhaseGate`.
+  - |
+    :meth:`.QuantumCircuit.depth` will now count the variables and clbits
+    used in real-time expressions as part of the depth calculation.
diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py
index 481f2fe3ca56..d51dd0c75616 100644
--- a/test/python/circuit/test_circuit_properties.py
+++ b/test/python/circuit/test_circuit_properties.py
@@ -17,7 +17,8 @@
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse
 from qiskit.circuit import Clbit
-from qiskit.circuit.library import RXGate, RYGate
+from qiskit.circuit.classical import expr, types
+from qiskit.circuit.library import RXGate, RYGate, GlobalPhaseGate
 from qiskit.circuit.exceptions import CircuitError
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
@@ -638,6 +639,58 @@ def test_circuit_depth_first_qubit(self):
         circ.measure(1, 0)
         self.assertEqual(circ.depth(lambda x: circ.qubits[0] in x.qubits), 3)
 
+    def test_circuit_depth_0_operands(self):
+        """Test that the depth can be found even with zero-bit operands."""
+        qc = QuantumCircuit(2, 2)
+        qc.append(GlobalPhaseGate(0.0), [], [])
+        qc.append(GlobalPhaseGate(0.0), [], [])
+        qc.append(GlobalPhaseGate(0.0), [], [])
+        self.assertEqual(qc.depth(), 0)
+        qc.measure([0, 1], [0, 1])
+        self.assertEqual(qc.depth(), 1)
+
+    def test_circuit_depth_expr_condition(self):
+        """Test that circuit depth respects `Expr` conditions in `IfElseOp`."""
+        # Note that the "depth" of control-flow operations is not well defined, so the assertions
+        # here are quite weak.  We're mostly aiming to match legacy behaviour of `c_if` for cases
+        # where there's a single instruction within the conditional.
+        qc = QuantumCircuit(2, 2)
+        a = qc.add_input("a", types.Bool())
+        with qc.if_test(a):
+            qc.x(0)
+        with qc.if_test(expr.logic_and(a, qc.clbits[0])):
+            qc.x(1)
+        self.assertEqual(qc.depth(), 2)
+        qc.measure([0, 1], [0, 1])
+        self.assertEqual(qc.depth(), 3)
+
+    def test_circuit_depth_expr_store(self):
+        """Test that circuit depth respects `Store`."""
+        qc = QuantumCircuit(3, 3)
+        a = qc.add_input("a", types.Bool())
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.measure([0, 1], [0, 1])
+        # Note that `Store` is a "directive", so doesn't increase the depth by default, but does
+        # cause qubits 0,1; clbits 0,1 and 'a' to all be depth 3 at this point.
+        qc.store(a, qc.clbits[0])
+        qc.store(a, expr.logic_and(a, qc.clbits[1]))
+        # ... so this use of 'a' should make it depth 4.
+        with qc.if_test(a):
+            qc.x(2)
+        self.assertEqual(qc.depth(), 4)
+
+    def test_circuit_depth_switch(self):
+        """Test that circuit depth respects the `target` of `SwitchCaseOp`."""
+        qc = QuantumCircuit(QuantumRegister(3, "q"), ClassicalRegister(3, "c"))
+        a = qc.add_input("a", types.Uint(3))
+
+        with qc.switch(expr.bit_and(a, qc.cregs[0])) as case:
+            with case(case.DEFAULT):
+                qc.x(0)
+        qc.measure(1, 0)
+        self.assertEqual(qc.depth(), 2)
+
     def test_circuit_size_empty(self):
         """Circuit.size should return 0 for an empty circuit."""
         size = 4

From 0b1c8bfd18d422a3bdb5ef3c2c4fff977cdbe390 Mon Sep 17 00:00:00 2001
From: shravanpatel30 <78003234+shravanpatel30@users.noreply.github.com>
Date: Fri, 7 Jun 2024 08:04:09 -0500
Subject: [PATCH 132/179] [unitaryHACK] Controlling the Insertion of
 Multi-Qubit Gates in the Generation of Random Circuits #12059 (#12483)

* unitaryHACK Controlling the Insertion of Multi-Qubit Gates in the Generation of Random Circuits #12059

* Fixed linting issues

* Fixed long lines and unused variable

* Added requested changes

* Removed unused imports

* Added a test

* Added the stochastic process comment and edited releasenotes

* Update qiskit/circuit/random/utils.py

* lint...

---------

Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
---
 qiskit/circuit/random/utils.py                | 123 ++++++++++++++----
 ...nded-random-circuits-049b67cce39003f4.yaml |  21 +++
 test/python/circuit/test_random_circuit.py    |  81 +++++++++++-
 3 files changed, 201 insertions(+), 24 deletions(-)
 create mode 100644 releasenotes/notes/extended-random-circuits-049b67cce39003f4.yaml

diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py
index fc497a300cba..f27cbfbfca88 100644
--- a/qiskit/circuit/random/utils.py
+++ b/qiskit/circuit/random/utils.py
@@ -21,7 +21,14 @@
 
 
 def random_circuit(
-    num_qubits, depth, max_operands=4, measure=False, conditional=False, reset=False, seed=None
+    num_qubits,
+    depth,
+    max_operands=4,
+    measure=False,
+    conditional=False,
+    reset=False,
+    seed=None,
+    num_operand_distribution: dict = None,
 ):
     """Generate random circuit of arbitrary size and form.
 
@@ -44,6 +51,10 @@ def random_circuit(
         conditional (bool): if True, insert middle measurements and conditionals
         reset (bool): if True, insert middle resets
         seed (int): sets random seed (optional)
+        num_operand_distribution (dict): a distribution of gates that specifies the ratio
+        of 1-qubit, 2-qubit, 3-qubit, ..., n-qubit gates in the random circuit. Expect a
+        deviation from the specified ratios that depends on the size of the requested
+        random circuit. (optional)
 
     Returns:
         QuantumCircuit: constructed circuit
@@ -51,11 +62,38 @@ def random_circuit(
     Raises:
         CircuitError: when invalid options given
     """
+    if seed is None:
+        seed = np.random.randint(0, np.iinfo(np.int32).max)
+    rng = np.random.default_rng(seed)
+
+    if num_operand_distribution:
+        if min(num_operand_distribution.keys()) < 1 or max(num_operand_distribution.keys()) > 4:
+            raise CircuitError("'num_operand_distribution' must have keys between 1 and 4")
+        for key, prob in num_operand_distribution.items():
+            if key > num_qubits and prob != 0.0:
+                raise CircuitError(
+                    f"'num_operand_distribution' cannot have {key}-qubit gates"
+                    f" for circuit with {num_qubits} qubits"
+                )
+        num_operand_distribution = dict(sorted(num_operand_distribution.items()))
+
+    if not num_operand_distribution and max_operands:
+        if max_operands < 1 or max_operands > 4:
+            raise CircuitError("max_operands must be between 1 and 4")
+        max_operands = max_operands if num_qubits > max_operands else num_qubits
+        rand_dist = rng.dirichlet(
+            np.ones(max_operands)
+        )  # This will create a random distribution that sums to 1
+        num_operand_distribution = {i + 1: rand_dist[i] for i in range(max_operands)}
+        num_operand_distribution = dict(sorted(num_operand_distribution.items()))
+
+    # Here we will use np.isclose() because very rarely there might be floating
+    # point precision errors
+    if not np.isclose(sum(num_operand_distribution.values()), 1):
+        raise CircuitError("The sum of all the values in 'num_operand_distribution' is not 1.")
+
     if num_qubits == 0:
         return QuantumCircuit()
-    if max_operands < 1 or max_operands > 4:
-        raise CircuitError("max_operands must be between 1 and 4")
-    max_operands = max_operands if num_qubits > max_operands else num_qubits
 
     gates_1q = [
         # (Gate class, number of qubits, number of parameters)
@@ -119,17 +157,26 @@ def random_circuit(
         (standard_gates.RC3XGate, 4, 0),
     ]
 
-    gates = gates_1q.copy()
-    if max_operands >= 2:
-        gates.extend(gates_2q)
-    if max_operands >= 3:
-        gates.extend(gates_3q)
-    if max_operands >= 4:
-        gates.extend(gates_4q)
-    gates = np.array(
-        gates, dtype=[("class", object), ("num_qubits", np.int64), ("num_params", np.int64)]
+    gates_1q = np.array(
+        gates_1q, dtype=[("class", object), ("num_qubits", np.int64), ("num_params", np.int64)]
     )
-    gates_1q = np.array(gates_1q, dtype=gates.dtype)
+    gates_2q = np.array(gates_2q, dtype=gates_1q.dtype)
+    gates_3q = np.array(gates_3q, dtype=gates_1q.dtype)
+    gates_4q = np.array(gates_4q, dtype=gates_1q.dtype)
+
+    all_gate_lists = [gates_1q, gates_2q, gates_3q, gates_4q]
+
+    # Here we will create a list 'gates_to_consider' that will have a
+    # subset of different n-qubit gates and will also create a list for
+    # ratio (or probability) for each gates
+    gates_to_consider = []
+    distribution = []
+    for n_qubits, ratio in num_operand_distribution.items():
+        gate_list = all_gate_lists[n_qubits - 1]
+        gates_to_consider.extend(gate_list)
+        distribution.extend([ratio / len(gate_list)] * len(gate_list))
+
+    gates = np.array(gates_to_consider, dtype=gates_1q.dtype)
 
     qc = QuantumCircuit(num_qubits)
 
@@ -137,29 +184,60 @@ def random_circuit(
         cr = ClassicalRegister(num_qubits, "c")
         qc.add_register(cr)
 
-    if seed is None:
-        seed = np.random.randint(0, np.iinfo(np.int32).max)
-    rng = np.random.default_rng(seed)
-
     qubits = np.array(qc.qubits, dtype=object, copy=True)
 
+    # Counter to keep track of number of different gate types
+    counter = np.zeros(len(all_gate_lists) + 1, dtype=np.int64)
+    total_gates = 0
+
     # Apply arbitrary random operations in layers across all qubits.
     for layer_number in range(depth):
         # We generate all the randomness for the layer in one go, to avoid many separate calls to
         # the randomisation routines, which can be fairly slow.
-
         # This reliably draws too much randomness, but it's less expensive than looping over more
         # calls to the rng. After, trim it down by finding the point when we've used all the qubits.
-        gate_specs = rng.choice(gates, size=len(qubits))
+
+        # Due to the stochastic nature of generating a random circuit, the resulting ratios
+        # may not precisely match the specified values from `num_operand_distribution`. Expect
+        # greater deviations from the target ratios in quantum circuits with fewer qubits and
+        # shallower depths, and smaller deviations in larger and deeper quantum circuits.
+        # For more information on how the distribution changes with number of qubits and depth
+        # refer to the pull request #12483 on Qiskit GitHub.
+
+        gate_specs = rng.choice(gates, size=len(qubits), p=distribution)
         cumulative_qubits = np.cumsum(gate_specs["num_qubits"], dtype=np.int64)
+
         # Efficiently find the point in the list where the total gates would use as many as
         # possible of, but not more than, the number of qubits in the layer.  If there's slack, fill
         # it with 1q gates.
         max_index = np.searchsorted(cumulative_qubits, num_qubits, side="right")
         gate_specs = gate_specs[:max_index]
+
         slack = num_qubits - cumulative_qubits[max_index - 1]
-        if slack:
-            gate_specs = np.hstack((gate_specs, rng.choice(gates_1q, size=slack)))
+
+        # Updating the counter for 1-qubit, 2-qubit, 3-qubit and 4-qubit gates
+        gate_qubits = gate_specs["num_qubits"]
+        counter += np.bincount(gate_qubits, minlength=len(all_gate_lists) + 1)
+
+        total_gates += len(gate_specs)
+
+        # Slack handling loop, this loop will add gates to fill
+        # the slack while respecting the 'num_operand_distribution'
+        while slack > 0:
+            gate_added_flag = False
+
+            for key, dist in sorted(num_operand_distribution.items(), reverse=True):
+                if slack >= key and counter[key] / total_gates < dist:
+                    gate_to_add = np.array(
+                        all_gate_lists[key - 1][rng.integers(0, len(all_gate_lists[key - 1]))]
+                    )
+                    gate_specs = np.hstack((gate_specs, gate_to_add))
+                    counter[key] += 1
+                    total_gates += 1
+                    slack -= key
+                    gate_added_flag = True
+            if not gate_added_flag:
+                break
 
         # For efficiency in the Python loop, this uses Numpy vectorisation to pre-calculate the
         # indices into the lists of qubits and parameters for every gate, and then suitably
@@ -202,7 +280,6 @@ def random_circuit(
             ):
                 operation = gate(*parameters[p_start:p_end])
                 qc._append(CircuitInstruction(operation=operation, qubits=qubits[q_start:q_end]))
-
     if measure:
         qc.measure(qc.qubits, cr)
 
diff --git a/releasenotes/notes/extended-random-circuits-049b67cce39003f4.yaml b/releasenotes/notes/extended-random-circuits-049b67cce39003f4.yaml
new file mode 100644
index 000000000000..f4bb585053bc
--- /dev/null
+++ b/releasenotes/notes/extended-random-circuits-049b67cce39003f4.yaml
@@ -0,0 +1,21 @@
+---
+features_circuits:
+  - |
+    The `random_circuit` function from `qiskit.circuit.random.utils` has a new feature where
+    users can specify a distribution `num_operand_distribution` (a dict) that specifies the
+    ratio of 1-qubit, 2-qubit, 3-qubit, and 4-qubit gates in the random circuit. For example,
+    if `num_operand_distribution = {1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25}` is passed to the function
+    then the generated circuit will have approximately 25% of 1-qubit, 2-qubit, 3-qubit, and
+    4-qubit gates (The order in which the dictionary is passed does not matter i.e. you can specify
+    `num_operand_distribution = {3: 0.5, 1: 0.0, 4: 0.3, 2: 0.2}` and the function will still work
+    as expected). Also it should be noted that the if `num_operand_distribution` is not specified
+    then `max_operands` will default to 4 and a random circuit with a random gate distribution will 
+    be generated. If both `num_operand_distribution` and `max_operands` are specified at the same 
+    time then `num_operand_distribution` will be used to generate the random circuit.
+    Example usage::
+
+           from qiskit.circuit.random import random_circuit
+
+           circ = random_circuit(num_qubits=6, depth=5, num_operand_distribution = {1: 0.25, 2: 0.25, 3: 0.25, 4: 0.25})
+           circ.draw(output='mpl')
+
diff --git a/test/python/circuit/test_random_circuit.py b/test/python/circuit/test_random_circuit.py
index deadcd09d692..ebbdfd28d648 100644
--- a/test/python/circuit/test_random_circuit.py
+++ b/test/python/circuit/test_random_circuit.py
@@ -12,6 +12,7 @@
 
 
 """Test random circuit generation utility."""
+import numpy as np
 from qiskit.circuit import QuantumCircuit, ClassicalRegister, Clbit
 from qiskit.circuit import Measure
 from qiskit.circuit.random import random_circuit
@@ -71,7 +72,7 @@ def test_large_conditional(self):
     def test_random_mid_circuit_measure_conditional(self):
         """Test random circuit with mid-circuit measurements for conditionals."""
         num_qubits = depth = 2
-        circ = random_circuit(num_qubits, depth, conditional=True, seed=4)
+        circ = random_circuit(num_qubits, depth, conditional=True, seed=16)
         self.assertEqual(circ.width(), 2 * num_qubits)
         op_names = [instruction.operation.name for instruction in circ]
         # Before a condition, there needs to be measurement in all the qubits.
@@ -81,3 +82,81 @@ def test_random_mid_circuit_measure_conditional(self):
             bool(getattr(instruction.operation, "condition", None)) for instruction in circ
         ]
         self.assertEqual([False, False, False, True], conditions)
+
+    def test_random_circuit_num_operand_distribution(self):
+        """Test that num_operand_distribution argument generates gates in correct proportion"""
+        num_qubits = 50
+        depth = 300
+        num_op_dist = {2: 0.25, 3: 0.25, 1: 0.25, 4: 0.25}
+        circ = random_circuit(
+            num_qubits, depth, num_operand_distribution=num_op_dist, seed=123456789
+        )
+        total_gates = circ.size()
+        self.assertEqual(circ.width(), num_qubits)
+        self.assertEqual(circ.depth(), depth)
+        gate_qubits = [instruction.operation.num_qubits for instruction in circ]
+        gate_type_counter = np.bincount(gate_qubits, minlength=5)
+        for gate_type, prob in sorted(num_op_dist.items()):
+            self.assertAlmostEqual(prob, gate_type_counter[gate_type] / total_gates, delta=0.1)
+
+    def test_random_circuit_2and3_qubit_gates_only(self):
+        """
+        Test that the generated random circuit only has 2 and 3 qubit gates,
+        while disallowing 1-qubit and 4-qubit gates if
+        num_operand_distribution = {2: some_prob, 3: some_prob}
+        """
+        num_qubits = 10
+        depth = 200
+        num_op_dist = {2: 0.5, 3: 0.5}
+        circ = random_circuit(num_qubits, depth, num_operand_distribution=num_op_dist, seed=200)
+        total_gates = circ.size()
+        gate_qubits = [instruction.operation.num_qubits for instruction in circ]
+        gate_type_counter = np.bincount(gate_qubits, minlength=5)
+        # Testing that the distribution of 2 and 3 qubit gate matches with given distribution
+        for gate_type, prob in sorted(num_op_dist.items()):
+            self.assertAlmostEqual(prob, gate_type_counter[gate_type] / total_gates, delta=0.1)
+        # Testing that there are no 1-qubit gate and 4-qubit in the generated random circuit
+        self.assertEqual(gate_type_counter[1], 0.0)
+        self.assertEqual(gate_type_counter[4], 0.0)
+
+    def test_random_circuit_3and4_qubit_gates_only(self):
+        """
+        Test that the generated random circuit only has 3 and 4 qubit gates,
+        while disallowing 1-qubit and 2-qubit gates if
+        num_operand_distribution = {3: some_prob, 4: some_prob}
+        """
+        num_qubits = 10
+        depth = 200
+        num_op_dist = {3: 0.5, 4: 0.5}
+        circ = random_circuit(
+            num_qubits, depth, num_operand_distribution=num_op_dist, seed=11111111
+        )
+        total_gates = circ.size()
+        gate_qubits = [instruction.operation.num_qubits for instruction in circ]
+        gate_type_counter = np.bincount(gate_qubits, minlength=5)
+        # Testing that the distribution of 3 and 4 qubit gate matches with given distribution
+        for gate_type, prob in sorted(num_op_dist.items()):
+            self.assertAlmostEqual(prob, gate_type_counter[gate_type] / total_gates, delta=0.1)
+        # Testing that there are no 1-qubit gate and 2-qubit in the generated random circuit
+        self.assertEqual(gate_type_counter[1], 0.0)
+        self.assertEqual(gate_type_counter[2], 0.0)
+
+    def test_random_circuit_with_zero_distribution(self):
+        """
+        Test that the generated random circuit only has 3 and 4 qubit gates,
+        while disallowing 1-qubit and 2-qubit gates if
+        num_operand_distribution = {1: 0.0, 2: 0.0, 3: some_prob, 4: some_prob}
+        """
+        num_qubits = 10
+        depth = 200
+        num_op_dist = {1: 0.0, 2: 0.0, 3: 0.5, 4: 0.5}
+        circ = random_circuit(num_qubits, depth, num_operand_distribution=num_op_dist, seed=12)
+        total_gates = circ.size()
+        gate_qubits = [instruction.operation.num_qubits for instruction in circ]
+        gate_type_counter = np.bincount(gate_qubits, minlength=5)
+        # Testing that the distribution of 3 and 4 qubit gate matches with given distribution
+        for gate_type, prob in sorted(num_op_dist.items()):
+            self.assertAlmostEqual(prob, gate_type_counter[gate_type] / total_gates, delta=0.1)
+        # Testing that there are no 1-qubit gate and 2-qubit in the generated random circuit
+        self.assertEqual(gate_type_counter[1], 0.0)
+        self.assertEqual(gate_type_counter[2], 0.0)

From 2b847b8e9b8ca21e6c0e1ce26acb0686aec3175a Mon Sep 17 00:00:00 2001
From: "Christopher J. Wood" 
Date: Mon, 10 Jun 2024 04:39:57 -0400
Subject: [PATCH 133/179] Fix bugs with VF2Layout pass and Qiskit Aer 0.13
 (#11585)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix bugs with V2FLayout pass and Qiskit Aer 0.13

* Update qiskit/transpiler/passes/layout/vf2_layout.py

Co-authored-by: Matthew Treinish 

* test

* Update releasenotes/notes/fix-vf2-aer-a7306ce07ea81700.yaml

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>

* long lines

---------

Co-authored-by: Luciano Bello 
Co-authored-by: Matthew Treinish 
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
---
 qiskit/transpiler/passes/layout/vf2_layout.py | 14 +++++++++----
 qiskit/transpiler/passes/layout/vf2_utils.py  |  4 ++--
 .../notes/fix-vf2-aer-a7306ce07ea81700.yaml   |  4 ++++
 test/python/transpiler/test_vf2_layout.py     | 20 +++++++++++++++++++
 4 files changed, 36 insertions(+), 6 deletions(-)
 create mode 100644 releasenotes/notes/fix-vf2-aer-a7306ce07ea81700.yaml

diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py
index 4e3077eb1d4d..626f8f2b0fa3 100644
--- a/qiskit/transpiler/passes/layout/vf2_layout.py
+++ b/qiskit/transpiler/passes/layout/vf2_layout.py
@@ -104,15 +104,21 @@ def __init__(
                 limit on the number of trials will be set.
             target (Target): A target representing the backend device to run ``VF2Layout`` on.
                 If specified it will supersede a set value for ``properties`` and
-                ``coupling_map``.
+                ``coupling_map`` if the :class:`.Target` contains connectivity constraints. If the value
+                of ``target`` models an ideal backend without any constraints then the value of
+                ``coupling_map``
+                will be used.
 
         Raises:
             TypeError: At runtime, if neither ``coupling_map`` or ``target`` are provided.
         """
         super().__init__()
         self.target = target
-        if target is not None:
-            self.coupling_map = self.target.build_coupling_map()
+        if (
+            target is not None
+            and (target_coupling_map := self.target.build_coupling_map()) is not None
+        ):
+            self.coupling_map = target_coupling_map
         else:
             self.coupling_map = coupling_map
         self.properties = properties
@@ -145,7 +151,7 @@ def run(self, dag):
         )
         # Filter qubits without any supported operations. If they don't support any operations
         # They're not valid for layout selection
-        if self.target is not None:
+        if self.target is not None and self.target.qargs is not None:
             has_operations = set(itertools.chain.from_iterable(self.target.qargs))
             to_remove = set(range(len(cm_nodes))).difference(has_operations)
             if to_remove:
diff --git a/qiskit/transpiler/passes/layout/vf2_utils.py b/qiskit/transpiler/passes/layout/vf2_utils.py
index 99006017482c..c5d420127f88 100644
--- a/qiskit/transpiler/passes/layout/vf2_utils.py
+++ b/qiskit/transpiler/passes/layout/vf2_utils.py
@@ -145,7 +145,7 @@ def score_layout(
 def build_average_error_map(target, properties, coupling_map):
     """Build an average error map used for scoring layouts pre-basis translation."""
     num_qubits = 0
-    if target is not None:
+    if target is not None and target.qargs is not None:
         num_qubits = target.num_qubits
         avg_map = ErrorMap(len(target.qargs))
     elif coupling_map is not None:
@@ -157,7 +157,7 @@ def build_average_error_map(target, properties, coupling_map):
         # object
         avg_map = ErrorMap(0)
     built = False
-    if target is not None:
+    if target is not None and target.qargs is not None:
         for qargs in target.qargs:
             if qargs is None:
                 continue
diff --git a/releasenotes/notes/fix-vf2-aer-a7306ce07ea81700.yaml b/releasenotes/notes/fix-vf2-aer-a7306ce07ea81700.yaml
new file mode 100644
index 000000000000..52ea96d0984b
--- /dev/null
+++ b/releasenotes/notes/fix-vf2-aer-a7306ce07ea81700.yaml
@@ -0,0 +1,4 @@
+fixes:
+  - |
+    The :class:`.VF2Layout` pass would raise an exception when provided with a :class:`.Target` instance without connectivity constraints.
+    This would be the case with targets from Aer 0.13. The issue is now fixed.
diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py
index b0957c824688..716e49d35009 100644
--- a/test/python/transpiler/test_vf2_layout.py
+++ b/test/python/transpiler/test_vf2_layout.py
@@ -570,6 +570,26 @@ def test_3_q_gate(self):
             pass_1.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.MORE_THAN_2Q
         )
 
+    def test_target_without_coupling_map(self):
+        """When a target has no coupling_map but it is provided as argument.
+        See: https://github.com/Qiskit/qiskit/pull/11585"""
+
+        circuit = QuantumCircuit(3)
+        circuit.cx(0, 1)
+        dag = circuit_to_dag(circuit)
+
+        target = Target(num_qubits=3)
+        target.add_instruction(CXGate())
+
+        vf2_pass = VF2Layout(
+            coupling_map=CouplingMap([[0, 2], [1, 2]]), target=target, seed=42, max_trials=1
+        )
+        vf2_pass.run(dag)
+
+        self.assertEqual(
+            vf2_pass.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.SOLUTION_FOUND
+        )
+
 
 class TestMultipleTrials(QiskitTestCase):
     """Test the passes behavior with >1 trial."""

From f4ca3da35e615e4732d5b561ae45bd83f7f5ac8d Mon Sep 17 00:00:00 2001
From: atharva-satpute <55058959+atharva-satpute@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:33:23 +0530
Subject: [PATCH 134/179] Fix broken BasisTranslator translation error link
 (#12533)

---
 qiskit/transpiler/passes/basis/basis_translator.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py
index 04bf852e55b5..bcac1f5d4faf 100644
--- a/qiskit/transpiler/passes/basis/basis_translator.py
+++ b/qiskit/transpiler/passes/basis/basis_translator.py
@@ -207,7 +207,7 @@ def run(self, dag):
                     "target basis is not universal or there are additional equivalence rules "
                     "needed in the EquivalenceLibrary being used. For more details on this "
                     "error see: "
-                    "https://docs.quantum.ibm.com/api/qiskit/transpiler_passes."
+                    "https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes."
                     "BasisTranslator#translation-errors"
                 )
 
@@ -225,7 +225,7 @@ def run(self, dag):
                 f"basis: {list(target_basis)}. This likely means the target basis is not universal "
                 "or there are additional equivalence rules needed in the EquivalenceLibrary being "
                 "used. For more details on this error see: "
-                "https://docs.quantum.ibm.com/api/qiskit/transpiler_passes."
+                "https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes."
                 "BasisTranslator#translation-errors"
             )
 

From b2c3ffd7383f14e71bdf6385213e9f6d5cda4021 Mon Sep 17 00:00:00 2001
From: Jim Garrison 
Date: Mon, 10 Jun 2024 06:39:18 -0400
Subject: [PATCH 135/179] Improve public type annotations for
 `OneQubitEulerDecomposer` (#12530)

---
 qiskit/synthesis/one_qubit/one_qubit_decompose.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/qiskit/synthesis/one_qubit/one_qubit_decompose.py b/qiskit/synthesis/one_qubit/one_qubit_decompose.py
index 5ca44d43d5b4..c84db761b7f0 100644
--- a/qiskit/synthesis/one_qubit/one_qubit_decompose.py
+++ b/qiskit/synthesis/one_qubit/one_qubit_decompose.py
@@ -14,6 +14,7 @@
 Decompose a single-qubit unitary via Euler angles.
 """
 from __future__ import annotations
+from typing import TYPE_CHECKING
 import numpy as np
 
 from qiskit._accelerate import euler_one_qubit_decomposer
@@ -37,6 +38,9 @@
 from qiskit.circuit.gate import Gate
 from qiskit.quantum_info.operators.operator import Operator
 
+if TYPE_CHECKING:
+    from qiskit.dagcircuit import DAGCircuit
+
 DEFAULT_ATOL = 1e-12
 
 ONE_QUBIT_EULER_BASIS_GATES = {
@@ -150,7 +154,7 @@ def __init__(self, basis: str = "U3", use_dag: bool = False):
         self.basis = basis  # sets: self._basis, self._params, self._circuit
         self.use_dag = use_dag
 
-    def build_circuit(self, gates, global_phase):
+    def build_circuit(self, gates, global_phase) -> QuantumCircuit | DAGCircuit:
         """Return the circuit or dag object from a list of gates."""
         qr = [Qubit()]
         lookup_gate = False
@@ -186,7 +190,7 @@ def __call__(
         unitary: Operator | Gate | np.ndarray,
         simplify: bool = True,
         atol: float = DEFAULT_ATOL,
-    ) -> QuantumCircuit:
+    ) -> QuantumCircuit | DAGCircuit:
         """Decompose single qubit gate into a circuit.
 
         Args:

From 1956220509e165b00396142236b260c49ee3fdbb Mon Sep 17 00:00:00 2001
From: jpacold 
Date: Mon, 10 Jun 2024 06:00:21 -0600
Subject: [PATCH 136/179] Move utility functions _inverse_pattern and
 _get_ordered_swap to Rust (#12327)

* Move utility functions _inverse_pattern and _get_ordered_swap to Rust

* fix formatting and pylint issues

* Changed input type to `PyArrayLike1`

* Refactor `permutation.rs`, clean up imports, fix coverage error

* fix docstring for `_inverse_pattern`

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* fix docstring for `_get_ordered_swap`

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* remove pymodule nesting

* remove explicit `AllowTypeChange`

* Move input validation out of `_inverse_pattern` and `_get_ordered_swap`

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
---
 crates/accelerate/src/lib.rs                  |   1 +
 crates/accelerate/src/permutation.rs          | 120 ++++++++++++++++++
 crates/pyext/src/lib.rs                       |   9 +-
 qiskit/__init__.py                            |   1 +
 .../permutation/permutation_utils.py          |  36 +-----
 .../synthesis/test_permutation_synthesis.py   |  48 ++++++-
 6 files changed, 179 insertions(+), 36 deletions(-)
 create mode 100644 crates/accelerate/src/permutation.rs

diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs
index 0af8ea6a0fce..3924c1de4092 100644
--- a/crates/accelerate/src/lib.rs
+++ b/crates/accelerate/src/lib.rs
@@ -23,6 +23,7 @@ pub mod isometry;
 pub mod nlayout;
 pub mod optimize_1q_gates;
 pub mod pauli_exp_val;
+pub mod permutation;
 pub mod results;
 pub mod sabre;
 pub mod sampled_exp_val;
diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/permutation.rs
new file mode 100644
index 000000000000..31ba433ddd30
--- /dev/null
+++ b/crates/accelerate/src/permutation.rs
@@ -0,0 +1,120 @@
+// 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 ndarray::{Array1, ArrayView1};
+use numpy::PyArrayLike1;
+use pyo3::exceptions::PyValueError;
+use pyo3::prelude::*;
+use std::vec::Vec;
+
+fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> {
+    let n = pattern.len();
+    let mut seen: Vec = vec![false; n];
+
+    for &x in pattern {
+        if x < 0 {
+            return Err(PyValueError::new_err(
+                "Invalid permutation: input contains a negative number.",
+            ));
+        }
+
+        if x as usize >= n {
+            return Err(PyValueError::new_err(format!(
+                "Invalid permutation: input has length {} and contains {}.",
+                n, x
+            )));
+        }
+
+        if seen[x as usize] {
+            return Err(PyValueError::new_err(format!(
+                "Invalid permutation: input contains {} more than once.",
+                x
+            )));
+        }
+
+        seen[x as usize] = true;
+    }
+
+    Ok(())
+}
+
+fn invert(pattern: &ArrayView1) -> Array1 {
+    let mut inverse: Array1 = Array1::zeros(pattern.len());
+    pattern.iter().enumerate().for_each(|(ii, &jj)| {
+        inverse[jj as usize] = ii;
+    });
+    inverse
+}
+
+fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> {
+    let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect();
+    let mut index_map = invert(pattern);
+
+    let n = permutation.len();
+    let mut swaps: Vec<(i64, i64)> = Vec::with_capacity(n);
+    for ii in 0..n {
+        let val = permutation[ii];
+        if val == ii {
+            continue;
+        }
+        let jj = index_map[ii];
+        swaps.push((ii as i64, jj as i64));
+        (permutation[ii], permutation[jj]) = (permutation[jj], permutation[ii]);
+        index_map[val] = jj;
+        index_map[ii] = ii;
+    }
+
+    swaps[..].reverse();
+    swaps
+}
+
+/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1.
+#[pyfunction]
+#[pyo3(signature = (pattern))]
+fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult {
+    let view = pattern.as_array();
+    validate_permutation(&view)?;
+    Ok(py.None())
+}
+
+/// Finds inverse of a permutation pattern.
+#[pyfunction]
+#[pyo3(signature = (pattern))]
+fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult {
+    let view = pattern.as_array();
+    let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect();
+    Ok(inverse_i64.to_object(py))
+}
+
+/// Sorts the input permutation by iterating through the permutation list
+/// and putting each element to its correct position via a SWAP (if it's not
+/// at the correct position already). If ``n`` is the length of the input
+/// permutation, this requires at most ``n`` SWAPs.
+///
+/// More precisely, if the input permutation is a cycle of length ``m``,
+/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``);
+/// if the input  permutation consists of several disjoint cycles, then each cycle
+/// is essentially treated independently.
+#[pyfunction]
+#[pyo3(signature = (permutation_in))]
+fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult {
+    let view = permutation_in.as_array();
+    Ok(get_ordered_swap(&view).to_object(py))
+}
+
+#[pymodule]
+pub fn permutation(m: &Bound) -> PyResult<()> {
+    m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
+    m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
+    m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?;
+    Ok(())
+}
diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs
index a21b1307a88f..b80aad1a7a45 100644
--- a/crates/pyext/src/lib.rs
+++ b/crates/pyext/src/lib.rs
@@ -17,10 +17,10 @@ use qiskit_accelerate::{
     convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout,
     error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer,
     isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates,
-    pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val,
-    sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap,
-    two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils,
-    vf2_layout::vf2_layout,
+    pauli_exp_val::pauli_expval, permutation::permutation, results::results, sabre::sabre,
+    sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op,
+    stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate,
+    utils::utils, vf2_layout::vf2_layout,
 };
 
 #[pymodule]
@@ -36,6 +36,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> {
     m.add_wrapped(wrap_pymodule!(nlayout))?;
     m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?;
     m.add_wrapped(wrap_pymodule!(pauli_expval))?;
+    m.add_wrapped(wrap_pymodule!(permutation))?;
     m.add_wrapped(wrap_pymodule!(results))?;
     m.add_wrapped(wrap_pymodule!(sabre))?;
     m.add_wrapped(wrap_pymodule!(sampled_exp_val))?;
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index e4fbc1729e53..27126de6df66 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -82,6 +82,7 @@
 sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap
 sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose
 sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout
+sys.modules["qiskit._accelerate.permutation"] = qiskit._accelerate.permutation
 
 from qiskit.exceptions import QiskitError, MissingOptionalLibraryError
 
diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py
index 6c6d950dc383..dbd73bfe8111 100644
--- a/qiskit/synthesis/permutation/permutation_utils.py
+++ b/qiskit/synthesis/permutation/permutation_utils.py
@@ -12,36 +12,12 @@
 
 """Utility functions for handling permutations."""
 
-
-def _get_ordered_swap(permutation_in):
-    """Sorts the input permutation by iterating through the permutation list
-    and putting each element to its correct position via a SWAP (if it's not
-    at the correct position already). If ``n`` is the length of the input
-    permutation, this requires at most ``n`` SWAPs.
-
-    More precisely, if the input permutation is a cycle of length ``m``,
-    then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``);
-    if the input  permutation consists of several disjoint cycles, then each cycle
-    is essentially treated independently.
-    """
-    permutation = list(permutation_in[:])
-    swap_list = []
-    index_map = _inverse_pattern(permutation_in)
-    for i, val in enumerate(permutation):
-        if val != i:
-            j = index_map[i]
-            swap_list.append((i, j))
-            permutation[i], permutation[j] = permutation[j], permutation[i]
-            index_map[val] = j
-            index_map[i] = i
-    swap_list.reverse()
-    return swap_list
-
-
-def _inverse_pattern(pattern):
-    """Finds inverse of a permutation pattern."""
-    b_map = {pos: idx for idx, pos in enumerate(pattern)}
-    return [b_map[pos] for pos in range(len(pattern))]
+# pylint: disable=unused-import
+from qiskit._accelerate.permutation import (
+    _inverse_pattern,
+    _get_ordered_swap,
+    _validate_permutation,
+)
 
 
 def _pattern_to_cycles(pattern):
diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py
index 5c4317ed58a3..a879d5251f90 100644
--- a/test/python/synthesis/test_permutation_synthesis.py
+++ b/test/python/synthesis/test_permutation_synthesis.py
@@ -25,7 +25,11 @@
     synth_permutation_basic,
     synth_permutation_reverse_lnn_kms,
 )
-from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap
+from qiskit.synthesis.permutation.permutation_utils import (
+    _inverse_pattern,
+    _get_ordered_swap,
+    _validate_permutation,
+)
 from test import QiskitTestCase  # pylint: disable=wrong-import-order
 
 
@@ -33,9 +37,19 @@
 class TestPermutationSynthesis(QiskitTestCase):
     """Test the permutation synthesis functions."""
 
+    @data(4, 5, 10, 15, 20)
+    def test_inverse_pattern(self, width):
+        """Test _inverse_pattern function produces correct index map."""
+        np.random.seed(1)
+        for _ in range(5):
+            pattern = np.random.permutation(width)
+            inverse = _inverse_pattern(pattern)
+            for ii, jj in enumerate(pattern):
+                self.assertTrue(inverse[jj] == ii)
+
     @data(4, 5, 10, 15, 20)
     def test_get_ordered_swap(self, width):
-        """Test get_ordered_swap function produces correct swap list."""
+        """Test _get_ordered_swap function produces correct swap list."""
         np.random.seed(1)
         for _ in range(5):
             pattern = np.random.permutation(width)
@@ -46,6 +60,36 @@ def test_get_ordered_swap(self, width):
             self.assertTrue(np.array_equal(pattern, output))
             self.assertLess(len(swap_list), width)
 
+    @data(10, 20)
+    def test_invalid_permutations(self, width):
+        """Check that _validate_permutation raises exceptions when the
+        input is not a permutation."""
+        np.random.seed(1)
+        for _ in range(5):
+            pattern = np.random.permutation(width)
+
+            pattern_out_of_range = np.copy(pattern)
+            pattern_out_of_range[0] = -1
+            with self.assertRaises(ValueError) as exc:
+                _validate_permutation(pattern_out_of_range)
+                self.assertIn("input contains a negative number", str(exc.exception))
+
+            pattern_out_of_range = np.copy(pattern)
+            pattern_out_of_range[0] = width
+            with self.assertRaises(ValueError) as exc:
+                _validate_permutation(pattern_out_of_range)
+                self.assertIn(
+                    "input has length {0} and contains {0}".format(width), str(exc.exception)
+                )
+
+            pattern_duplicate = np.copy(pattern)
+            pattern_duplicate[-1] = pattern[0]
+            with self.assertRaises(ValueError) as exc:
+                _validate_permutation(pattern_duplicate)
+                self.assertIn(
+                    "input contains {} more than once".format(pattern[0]), str(exc.exception)
+                )
+
     @data(4, 5, 10, 15, 20)
     def test_synth_permutation_basic(self, width):
         """Test synth_permutation_basic function produces the correct

From d56a93325acf258552fb54d474eb7466a6bea7f6 Mon Sep 17 00:00:00 2001
From: "S.S" <66886825+EarlMilktea@users.noreply.github.com>
Date: Mon, 10 Jun 2024 21:19:42 +0900
Subject: [PATCH 137/179] Fix annotation (#12535)

* :bug: Fix annotation

* :art: Format by black
---
 qiskit/providers/providerutils.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/qiskit/providers/providerutils.py b/qiskit/providers/providerutils.py
index 1e65499d756a..36592dc1bc69 100644
--- a/qiskit/providers/providerutils.py
+++ b/qiskit/providers/providerutils.py
@@ -21,7 +21,9 @@
 logger = logging.getLogger(__name__)
 
 
-def filter_backends(backends: list[Backend], filters: Callable = None, **kwargs) -> list[Backend]:
+def filter_backends(
+    backends: list[Backend], filters: Callable[[Backend], bool] | None = None, **kwargs
+) -> list[Backend]:
     """Return the backends matching the specified filtering.
 
     Filter the `backends` list by their `configuration` or `status`

From b83efff3b4116406e1875cbaf89f579d7e6e4c53 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Mon, 10 Jun 2024 12:27:28 -0400
Subject: [PATCH 138/179] Moving group of lint rules (#12315)

* Moving the arguments-renamed, no-member, no-value-for-parameter, not-context-manager, unexpected-keyword-arg, unnecessary-dunder-call, unnecessary-lambda-assignment, and unspecified-encoding lint rules

* updates based on PR and moving not-context-manager back to temp disabled

* inline disable no-value-for-parameter lint exception

* "unnecessary-dunder-call" wrongly removed

---------

Co-authored-by: Luciano Bello 
---
 pyproject.toml                          | 11 ++++-------
 qiskit/primitives/backend_estimator.py  |  2 +-
 qiskit/primitives/backend_sampler.py    |  2 +-
 qiskit/providers/options.py             |  2 +-
 qiskit/transpiler/basepasses.py         |  2 +-
 qiskit/transpiler/passmanager.py        |  6 +++---
 test/python/circuit/test_unitary.py     |  2 +-
 test/python/compiler/test_transpiler.py |  2 +-
 8 files changed, 13 insertions(+), 16 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6e57fa53a7e8..fe6036d3abea 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -209,6 +209,7 @@ disable = [
     "too-many-lines", "too-many-branches", "too-many-locals", "too-many-nested-blocks", "too-many-statements",
     "too-many-instance-attributes", "too-many-arguments", "too-many-public-methods", "too-few-public-methods", "too-many-ancestors",
     "unnecessary-pass", # allow for methods with just "pass", for clarity
+    "unnecessary-dunder-call", # do not want to implement
     "no-else-return",  # relax "elif" after a clause with a return
     "docstring-first-line-empty", # relax docstring style
     "import-outside-toplevel", "import-error", # overzealous with our optionals/dynamic packages
@@ -217,17 +218,13 @@ disable = [
 # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so,
 #  remove from here and fix the issues. Else, move it above this section and add a comment
 #  with the rationale
-    "arguments-renamed",
     "consider-using-enumerate",
     "consider-using-f-string",
-    "no-member",
-    "no-value-for-parameter",
+    "no-member",  # for dynamically created members
     "not-context-manager",
     "possibly-used-before-assignment",
-    "unexpected-keyword-arg",
-    "unnecessary-dunder-call",
-    "unnecessary-lambda-assignment",
-    "unspecified-encoding",
+    "unnecessary-lambda-assignment",  # do not want to implement
+    "unspecified-encoding",  # do not want to implement
 ]
 
 enable = [
diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py
index 23556e2efe27..b91ea7068be1 100644
--- a/qiskit/primitives/backend_estimator.py
+++ b/qiskit/primitives/backend_estimator.py
@@ -198,7 +198,7 @@ def _transpile(self):
                 transpiled_circuit = common_circuit.copy()
                 final_index_layout = list(range(common_circuit.num_qubits))
             else:
-                transpiled_circuit = transpile(
+                transpiled_circuit = transpile(  # pylint:disable=unexpected-keyword-arg
                     common_circuit, self.backend, **self.transpile_options.__dict__
                 )
                 if transpiled_circuit.layout is not None:
diff --git a/qiskit/primitives/backend_sampler.py b/qiskit/primitives/backend_sampler.py
index 94c1c3c88b54..f1399a548939 100644
--- a/qiskit/primitives/backend_sampler.py
+++ b/qiskit/primitives/backend_sampler.py
@@ -176,7 +176,7 @@ def _transpile(self):
 
         start = len(self._transpiled_circuits)
         self._transpiled_circuits.extend(
-            transpile(
+            transpile(  # pylint:disable=unexpected-keyword-arg
                 self.preprocessed_circuits[start:],
                 self.backend,
                 **self.transpile_options.__dict__,
diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py
index 659af518a2b7..8b2bffc52d84 100644
--- a/qiskit/providers/options.py
+++ b/qiskit/providers/options.py
@@ -154,7 +154,7 @@ def __copy__(self):
 
         The returned option and validator values are shallow copies of the originals.
         """
-        out = self.__new__(type(self))
+        out = self.__new__(type(self))  # pylint:disable=no-value-for-parameter
         out.__setstate__((self._fields.copy(), self.validator.copy()))
         return out
 
diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py
index c09ee190e38b..396f5cf49344 100644
--- a/qiskit/transpiler/basepasses.py
+++ b/qiskit/transpiler/basepasses.py
@@ -87,7 +87,7 @@ def __eq__(self, other):
         return hash(self) == hash(other)
 
     @abstractmethod
-    def run(self, dag: DAGCircuit):  # pylint: disable=arguments-differ
+    def run(self, dag: DAGCircuit):  # pylint:disable=arguments-renamed
         """Run a pass on the DAGCircuit. This is implemented by the pass developer.
 
         Args:
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index 96c0be11b448..eec148e8db2d 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -139,7 +139,7 @@ def _finalize_layouts(self, dag):
         self.property_set["layout"] = t_initial_layout
         self.property_set["final_layout"] = new_final_layout
 
-    def append(
+    def append(  # pylint:disable=arguments-renamed
         self,
         passes: Task | list[Task],
     ) -> None:
@@ -153,7 +153,7 @@ def append(
         """
         super().append(tasks=passes)
 
-    def replace(
+    def replace(  # pylint:disable=arguments-renamed
         self,
         index: int,
         passes: Task | list[Task],
@@ -167,7 +167,7 @@ def replace(
         super().replace(index, tasks=passes)
 
     # pylint: disable=arguments-differ
-    def run(
+    def run(  # pylint:disable=arguments-renamed
         self,
         circuits: _CircuitsT,
         output_name: str | None = None,
diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py
index 23aec666cbdc..c5c9344ad7ed 100644
--- a/test/python/circuit/test_unitary.py
+++ b/test/python/circuit/test_unitary.py
@@ -178,7 +178,7 @@ def test_qobj_with_unitary_matrix(self):
         class NumpyEncoder(json.JSONEncoder):
             """Class for encoding json str with complex and numpy arrays."""
 
-            def default(self, obj):
+            def default(self, obj):  # pylint:disable=arguments-renamed
                 if isinstance(obj, numpy.ndarray):
                     return obj.tolist()
                 if isinstance(obj, complex):
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
index 30ba83440c9e..6058f922e171 100644
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -2864,7 +2864,7 @@ def max_circuits(self):
             def _default_options(cls):
                 return Options(shots=1024)
 
-            def run(self, circuit, **kwargs):
+            def run(self, circuit, **kwargs):  # pylint:disable=arguments-renamed
                 raise NotImplementedError
 
         self.backend = FakeMultiChip()

From 03d107e1f68f2a47ffdcf5599f906120e4efba63 Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Mon, 10 Jun 2024 16:39:48 -0400
Subject: [PATCH 139/179] Removing consider-using-enumerate from lint
 exclusions and updates (#12286)

* removing consider-using-enumerate from lint exclusions and updates

* updating for lint removal of consider-using-enumerate

* adding disable lint comment for consider-using-enumerate

* updating loops for getting the original, ordered list of qubits in passmanager.py

* reverting update and adding disable comment
---
 pyproject.toml                                |  1 -
 .../piecewise_polynomial_pauli_rotations.py   |  4 +-
 .../stabilizer/stabilizer_circuit.py          | 12 +++---
 .../optimization/inverse_cancellation.py      |  8 ++--
 .../template_matching/forward_match.py        |  4 +-
 qiskit/transpiler/passmanager.py              |  1 +
 qiskit/visualization/bloch.py                 | 37 ++++++++-----------
 test/python/synthesis/test_synthesis.py       |  6 +--
 8 files changed, 34 insertions(+), 39 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index fe6036d3abea..c13486c21bb3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -218,7 +218,6 @@ disable = [
 # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so,
 #  remove from here and fix the issues. Else, move it above this section and add a comment
 #  with the rationale
-    "consider-using-enumerate",
     "consider-using-f-string",
     "no-member",  # for dynamically created members
     "not-context-manager",
diff --git a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
index f604e16f469a..7e79ed04da12 100644
--- a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
+++ b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
@@ -218,8 +218,8 @@ def evaluate(self, x: float) -> float:
         """
 
         y = 0
-        for i in range(0, len(self.breakpoints)):
-            y = y + (x >= self.breakpoints[i]) * (np.poly1d(self.mapped_coeffs[i][::-1])(x))
+        for i, breakpt in enumerate(self.breakpoints):
+            y = y + (x >= breakpt) * (np.poly1d(self.mapped_coeffs[i][::-1])(x))
 
         return y
 
diff --git a/qiskit/synthesis/stabilizer/stabilizer_circuit.py b/qiskit/synthesis/stabilizer/stabilizer_circuit.py
index 3882676be7de..4a5d53a73226 100644
--- a/qiskit/synthesis/stabilizer/stabilizer_circuit.py
+++ b/qiskit/synthesis/stabilizer/stabilizer_circuit.py
@@ -68,8 +68,8 @@ def synth_circuit_from_stabilizers(
     circuit = QuantumCircuit(num_qubits)
 
     used = 0
-    for i in range(len(stabilizer_list)):
-        curr_stab = stabilizer_list[i].evolve(Clifford(circuit), frame="s")
+    for i, stabilizer in enumerate(stabilizer_list):
+        curr_stab = stabilizer.evolve(Clifford(circuit), frame="s")
 
         # Find pivot.
         pivot = used
@@ -81,17 +81,17 @@ def synth_circuit_from_stabilizers(
         if pivot == num_qubits:
             if curr_stab.x.any():
                 raise QiskitError(
-                    f"Stabilizer {i} ({stabilizer_list[i]}) anti-commutes with some of "
+                    f"Stabilizer {i} ({stabilizer}) anti-commutes with some of "
                     "the previous stabilizers."
                 )
             if curr_stab.phase == 2:
                 raise QiskitError(
-                    f"Stabilizer {i} ({stabilizer_list[i]}) contradicts "
+                    f"Stabilizer {i} ({stabilizer}) contradicts "
                     "some of the previous stabilizers."
                 )
             if curr_stab.z.any() and not allow_redundant:
                 raise QiskitError(
-                    f"Stabilizer {i} ({stabilizer_list[i]}) is a product of the others "
+                    f"Stabilizer {i} ({stabilizer}) is a product of the others "
                     "and allow_redundant is False. Add allow_redundant=True "
                     "to the function call if you want to allow redundant stabilizers."
                 )
@@ -133,7 +133,7 @@ def synth_circuit_from_stabilizers(
             circuit.swap(pivot, used)
 
         # fix sign
-        curr_stab = stabilizer_list[i].evolve(Clifford(circuit), frame="s")
+        curr_stab = stabilizer.evolve(Clifford(circuit), frame="s")
         if curr_stab.phase == 2:
             circuit.x(used)
         used += 1
diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py
index c814f50d4a18..958f53ef057d 100644
--- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py
+++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py
@@ -112,15 +112,15 @@ def _run_on_self_inverse(self, dag: DAGCircuit):
                 partitions = []
                 chunk = []
                 max_index = len(gate_cancel_run) - 1
-                for i in range(len(gate_cancel_run)):
-                    if gate_cancel_run[i].op == gate:
-                        chunk.append(gate_cancel_run[i])
+                for i, cancel_gate in enumerate(gate_cancel_run):
+                    if cancel_gate.op == gate:
+                        chunk.append(cancel_gate)
                     else:
                         if chunk:
                             partitions.append(chunk)
                             chunk = []
                         continue
-                    if i == max_index or gate_cancel_run[i].qargs != gate_cancel_run[i + 1].qargs:
+                    if i == max_index or cancel_gate.qargs != gate_cancel_run[i + 1].qargs:
                         partitions.append(chunk)
                         chunk = []
                 # Remove an even number of gates from each chunk
diff --git a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py
index 627db502d33e..d8dd5bb2b9a2 100644
--- a/qiskit/transpiler/passes/optimization/template_matching/forward_match.py
+++ b/qiskit/transpiler/passes/optimization/template_matching/forward_match.py
@@ -138,8 +138,8 @@ def _find_forward_candidates(self, node_id_t):
         """
         matches = []
 
-        for i in range(0, len(self.match)):
-            matches.append(self.match[i][0])
+        for match in self.match:
+            matches.append(match[0])
 
         pred = matches.copy()
         if len(pred) > 1:
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index eec148e8db2d..f590a1510bda 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -121,6 +121,7 @@ def _finalize_layouts(self, dag):
         # Ordered list of original qubits
         original_qubits_reverse = {v: k for k, v in original_qubit_indices.items()}
         original_qubits = []
+        # pylint: disable-next=consider-using-enumerate
         for i in range(len(original_qubits_reverse)):
             original_qubits.append(original_qubits_reverse[i])
 
diff --git a/qiskit/visualization/bloch.py b/qiskit/visualization/bloch.py
index 513d2ddff85e..2855c6ba9651 100644
--- a/qiskit/visualization/bloch.py
+++ b/qiskit/visualization/bloch.py
@@ -587,11 +587,11 @@ def plot_axes_labels(self):
     def plot_vectors(self):
         """Plot vector"""
         # -X and Y data are switched for plotting purposes
-        for k in range(len(self.vectors)):
+        for k, vector in enumerate(self.vectors):
 
-            xs3d = self.vectors[k][1] * np.array([0, 1])
-            ys3d = -self.vectors[k][0] * np.array([0, 1])
-            zs3d = self.vectors[k][2] * np.array([0, 1])
+            xs3d = vector[1] * np.array([0, 1])
+            ys3d = -vector[0] * np.array([0, 1])
+            zs3d = vector[2] * np.array([0, 1])
 
             color = self.vector_color[np.mod(k, len(self.vector_color))]
 
@@ -617,15 +617,10 @@ def plot_vectors(self):
     def plot_points(self):
         """Plot points"""
         # -X and Y data are switched for plotting purposes
-        for k in range(len(self.points)):
-            num = len(self.points[k][0])
+        for k, point in enumerate(self.points):
+            num = len(point[0])
             dist = [
-                np.sqrt(
-                    self.points[k][0][j] ** 2
-                    + self.points[k][1][j] ** 2
-                    + self.points[k][2][j] ** 2
-                )
-                for j in range(num)
+                np.sqrt(point[0][j] ** 2 + point[1][j] ** 2 + point[2][j] ** 2) for j in range(num)
             ]
             if any(abs(dist - dist[0]) / dist[0] > 1e-12):
                 # combine arrays so that they can be sorted together
@@ -637,9 +632,9 @@ def plot_points(self):
                 indperm = np.arange(num)
             if self.point_style[k] == "s":
                 self.axes.scatter(
-                    np.real(self.points[k][1][indperm]),
-                    -np.real(self.points[k][0][indperm]),
-                    np.real(self.points[k][2][indperm]),
+                    np.real(point[1][indperm]),
+                    -np.real(point[0][indperm]),
+                    np.real(point[2][indperm]),
                     s=self.point_size[np.mod(k, len(self.point_size))],
                     alpha=1,
                     edgecolor=None,
@@ -656,9 +651,9 @@ def plot_points(self):
                 marker = self.point_marker[np.mod(k, len(self.point_marker))]
                 pnt_size = self.point_size[np.mod(k, len(self.point_size))]
                 self.axes.scatter(
-                    np.real(self.points[k][1][indperm]),
-                    -np.real(self.points[k][0][indperm]),
-                    np.real(self.points[k][2][indperm]),
+                    np.real(point[1][indperm]),
+                    -np.real(point[0][indperm]),
+                    np.real(point[2][indperm]),
                     s=pnt_size,
                     alpha=1,
                     edgecolor=None,
@@ -670,9 +665,9 @@ def plot_points(self):
             elif self.point_style[k] == "l":
                 color = self.point_color[np.mod(k, len(self.point_color))]
                 self.axes.plot(
-                    np.real(self.points[k][1]),
-                    -np.real(self.points[k][0]),
-                    np.real(self.points[k][2]),
+                    np.real(point[1]),
+                    -np.real(point[0]),
+                    np.real(point[2]),
                     alpha=0.75,
                     zdir="z",
                     color=color,
diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py
index cb918b29146a..8d953ff1b83c 100644
--- a/test/python/synthesis/test_synthesis.py
+++ b/test/python/synthesis/test_synthesis.py
@@ -138,13 +138,13 @@ def assertDebugOnly(self):  # FIXME: when at python 3.10+ replace with assertNoL
         """Context manager, asserts log is emitted at level DEBUG but no higher"""
         with self.assertLogs("qiskit.synthesis", "DEBUG") as ctx:
             yield
-        for i in range(len(ctx.records)):
+        for i, record in enumerate(ctx.records):
             self.assertLessEqual(
-                ctx.records[i].levelno,
+                record.levelno,
                 logging.DEBUG,
                 msg=f"Unexpected logging entry: {ctx.output[i]}",
             )
-            self.assertIn("Requested fidelity:", ctx.records[i].getMessage())
+            self.assertIn("Requested fidelity:", record.getMessage())
 
     def assertRoundTrip(self, weyl1: TwoQubitWeylDecomposition):
         """Fail if eval(repr(weyl1)) not equal to weyl1"""

From bc685d3002915327f3070ef8914c6c1484084b57 Mon Sep 17 00:00:00 2001
From: Will Shanks 
Date: Tue, 11 Jun 2024 11:58:34 -0400
Subject: [PATCH 140/179] Use hash of numeric value for bound parameter
 expressions (#12488)

* Use hash of numeric value for bound parameter expressions

If a `ParameterExpression` has no unbound parameters, the underlying
bound value can be hashed instead of the tuple that accounts for the
symbolic expression. Doing this allows for the `ParameterExpression` to
match the hash for the numeric value it compares equal to.

Closes #12487

* Add release note
---
 qiskit/circuit/parameterexpression.py                     | 3 +++
 .../notes/parameterexpression-hash-d2593ab1715aa42c.yaml  | 8 ++++++++
 test/python/circuit/test_parameters.py                    | 1 +
 3 files changed, 12 insertions(+)
 create mode 100644 releasenotes/notes/parameterexpression-hash-d2593ab1715aa42c.yaml

diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
index 2b81ddd769f6..f881e09333d5 100644
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -442,6 +442,9 @@ def __int__(self):
             raise TypeError("could not cast expression to int") from exc
 
     def __hash__(self):
+        if not self._parameter_symbols:
+            # For fully bound expressions, fall back to the underlying value
+            return hash(self.numeric())
         return hash((self._parameter_keys, self._symbol_expr))
 
     def __copy__(self):
diff --git a/releasenotes/notes/parameterexpression-hash-d2593ab1715aa42c.yaml b/releasenotes/notes/parameterexpression-hash-d2593ab1715aa42c.yaml
new file mode 100644
index 000000000000..075de45b3b23
--- /dev/null
+++ b/releasenotes/notes/parameterexpression-hash-d2593ab1715aa42c.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+  - |
+    :class:`.ParameterExpression` was updated so that fully bound instances
+    that compare equal to instances of Python's built-in numeric types (like
+    ``float`` and ``int``) also have hash values that match those of the other
+    instances. This change ensures that these types can be used interchangeably
+    as dictionary keys. See `#12488 `__.
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
index ed82f33eac97..c9380fd07689 100644
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -1425,6 +1425,7 @@ def test_compare_to_value_when_bound(self):
         x = Parameter("x")
         bound_expr = x.bind({x: 2.3})
         self.assertEqual(bound_expr, 2.3)
+        self.assertEqual(hash(bound_expr), hash(2.3))
 
     def test_abs_function_when_bound(self):
         """Verify expression can be used with

From b933f6d377e7e7535e0fa4ee202ce97d6461b6b1 Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Wed, 12 Jun 2024 11:14:48 +0200
Subject: [PATCH 141/179] Add option to user config to control `idle_wires` in
 circuit drawer (#12462)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add option to user config to control idle_wires in circuit drawer

Co-Authored-By: diemilio 

* docs

* 11339

* Update qiskit/visualization/circuit/circuit_visualization.py

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>

---------

Co-authored-by: diemilio 
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
---
 qiskit/circuit/quantumcircuit.py              | 28 +++++-----
 qiskit/user_config.py                         | 14 +++++
 .../circuit/circuit_visualization.py          | 12 +++-
 .../workaroud_12361-994d0ac2d2a6ed41.yaml     | 14 +++++
 test/python/test_user_config.py               | 29 ++++++++++
 .../transpiler/test_basis_translator.py       |  9 +--
 .../visualization/test_circuit_text_drawer.py | 55 ++++++++++++++++++-
 7 files changed, 139 insertions(+), 22 deletions(-)
 create mode 100644 releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml

diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index ea4361fd8255..606d0e04373a 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1757,14 +1757,14 @@ def compose(
                 this can be anything that :obj:`.append` will accept.
             qubits (list[Qubit|int]): qubits of self to compose onto.
             clbits (list[Clbit|int]): clbits of self to compose onto.
-            front (bool): If True, front composition will be performed.  This is not possible within
+            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.
+            inplace (bool): If ``True``, modify the object. Otherwise, return composed circuit.
             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
                 the base circuit, in order to avoid unnecessary copies; in this case, it is not
-                valid to use ``other`` afterwards, and some instructions may have been mutated in
+                valid to use ``other`` afterward, and some instructions may have been mutated in
                 place.
             var_remap (Mapping): mapping to use to rewrite :class:`.expr.Var` nodes in ``other`` as
                 they are inlined into ``self``.  This can be used to avoid naming conflicts.
@@ -2068,7 +2068,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu
 
         Args:
             other (QuantumCircuit): The other circuit to tensor this circuit with.
-            inplace (bool): If True, modify the object. Otherwise return composed circuit.
+            inplace (bool): If ``True``, modify the object. Otherwise return composed circuit.
 
         Examples:
 
@@ -2084,7 +2084,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu
                tensored.draw('mpl')
 
         Returns:
-            QuantumCircuit: The tensored circuit (returns None if inplace==True).
+            QuantumCircuit: The tensored circuit (returns ``None`` if ``inplace=True``).
         """
         num_qubits = self.num_qubits + other.num_qubits
         num_clbits = self.num_clbits + other.num_clbits
@@ -3126,7 +3126,7 @@ def draw(
         reverse_bits: bool | None = None,
         justify: str | None = None,
         vertical_compression: str | None = "medium",
-        idle_wires: bool = True,
+        idle_wires: bool | None = None,
         with_layout: bool = True,
         fold: int | None = None,
         # The type of ax is matplotlib.axes.Axes, but this is not a fixed dependency, so cannot be
@@ -3157,7 +3157,7 @@ def draw(
         Args:
             output: Select the output method to use for drawing the circuit.
                 Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``.
-                By default the `text` drawer is used unless the user config file
+                By default, the ``text`` drawer is used unless the user config file
                 (usually ``~/.qiskit/settings.conf``) has an alternative backend set
                 as the default. For example, ``circuit_drawer = latex``. If the output
                 kwarg is set, that backend will always be used over the default in
@@ -3203,7 +3203,9 @@ def draw(
                 will take less vertical room.  Default is ``medium``. Only used by
                 the ``text`` output, will be silently ignored otherwise.
             idle_wires: Include idle wires (wires with no circuit elements)
-                in output visualization. Default is ``True``.
+                in output visualization. Default is ``True`` unless the
+                user config file (usually ``~/.qiskit/settings.conf``) has an
+                alternative value set. For example, ``circuit_idle_wires = False``.
             with_layout: Include layout information, with labels on the
                 physical layout. Default is ``True``.
             fold: Sets pagination. It can be disabled using -1. In ``text``,
@@ -3292,7 +3294,7 @@ def size(
         Args:
             filter_function (callable): a function to filter out some instructions.
                 Should take as input a tuple of (Instruction, list(Qubit), list(Clbit)).
-                By default filters out "directives", such as barrier or snapshot.
+                By default, filters out "directives", such as barrier or snapshot.
 
         Returns:
             int: Total number of gate operations.
@@ -3314,7 +3316,7 @@ def depth(
             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
+                computation of the circuit depth.  By default, filters out "directives", such as
                 :class:`.Barrier`.
 
         Returns:
@@ -3445,7 +3447,7 @@ def num_connected_components(self, unitary_only: bool = False) -> int:
         bits = self.qubits if unitary_only else (self.qubits + self.clbits)
         bit_indices: dict[Qubit | Clbit, int] = {bit: idx for idx, bit in enumerate(bits)}
 
-        # Start with each qubit or cbit being its own subgraph.
+        # Start with each qubit or clbit being its own subgraph.
         sub_graphs = [[bit] for bit in range(len(bit_indices))]
 
         num_sub_graphs = len(sub_graphs)
@@ -3816,7 +3818,7 @@ def measure_active(self, inplace: bool = True) -> Optional["QuantumCircuit"]:
             inplace (bool): All measurements inplace or return new circuit.
 
         Returns:
-            QuantumCircuit: Returns circuit with measurements when `inplace = False`.
+            QuantumCircuit: Returns circuit with measurements when ``inplace = False``.
         """
         from qiskit.converters.circuit_to_dag import circuit_to_dag
 
@@ -5704,7 +5706,7 @@ class to prepare the qubits in a specified state.
                 * Statevector or vector of complex amplitudes to initialize to.
                 * Labels of basis states of the Pauli eigenstates Z, X, Y. See
                   :meth:`.Statevector.from_label`. Notice the order of the labels is reversed with
-                  respect to the qubit index to be applied to. Example label '01' initializes the
+                  respect to the qubit index to be applied to. Example label ``'01'`` initializes the
                   qubit zero to :math:`|1\rangle` and the qubit one to :math:`|0\rangle`.
                 * An integer that is used as a bitmap indicating which qubits to initialize to
                   :math:`|1\rangle`. Example: setting params to 5 would initialize qubit 0 and qubit
diff --git a/qiskit/user_config.py b/qiskit/user_config.py
index 666bf53d962b..73a68eb6bfd5 100644
--- a/qiskit/user_config.py
+++ b/qiskit/user_config.py
@@ -31,6 +31,7 @@ class UserConfig:
     circuit_mpl_style = default
     circuit_mpl_style_path = ~/.qiskit:
     circuit_reverse_bits = True
+    circuit_idle_wires = False
     transpile_optimization_level = 1
     parallel = False
     num_processes = 4
@@ -130,6 +131,18 @@ def read_config_file(self):
             if circuit_reverse_bits is not None:
                 self.settings["circuit_reverse_bits"] = circuit_reverse_bits
 
+            # Parse circuit_idle_wires
+            try:
+                circuit_idle_wires = self.config_parser.getboolean(
+                    "default", "circuit_idle_wires", fallback=None
+                )
+            except ValueError as err:
+                raise exceptions.QiskitUserConfigError(
+                    f"Value assigned to circuit_idle_wires is not valid. {str(err)}"
+                )
+            if circuit_idle_wires is not None:
+                self.settings["circuit_idle_wires"] = circuit_idle_wires
+
             # Parse transpile_optimization_level
             transpile_optimization_level = self.config_parser.getint(
                 "default", "transpile_optimization_level", fallback=-1
@@ -191,6 +204,7 @@ def set_config(key, value, section=None, file_path=None):
         "circuit_mpl_style",
         "circuit_mpl_style_path",
         "circuit_reverse_bits",
+        "circuit_idle_wires",
         "transpile_optimization_level",
         "parallel",
         "num_processes",
diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py
index bea6021c23a8..a1672dc1676c 100644
--- a/qiskit/visualization/circuit/circuit_visualization.py
+++ b/qiskit/visualization/circuit/circuit_visualization.py
@@ -63,7 +63,7 @@ def circuit_drawer(
     reverse_bits: bool | None = None,
     justify: str | None = None,
     vertical_compression: str | None = "medium",
-    idle_wires: bool = True,
+    idle_wires: bool | None = None,
     with_layout: bool = True,
     fold: int | None = None,
     # The type of ax is matplotlib.axes.Axes, but this is not a fixed dependency, so cannot be
@@ -115,7 +115,7 @@ def circuit_drawer(
 
         output: Select the output method to use for drawing the circuit.
             Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``.
-            By default the `text` drawer is used unless the user config file
+            By default, the ``text`` drawer is used unless the user config file
             (usually ``~/.qiskit/settings.conf``) has an alternative backend set
             as the default. For example, ``circuit_drawer = latex``. If the output
             kwarg is set, that backend will always be used over the default in
@@ -141,7 +141,9 @@ def circuit_drawer(
             will take less vertical room.  Default is ``medium``. Only used by
             the ``text`` output, will be silently ignored otherwise.
         idle_wires: Include idle wires (wires with no circuit elements)
-            in output visualization. Default is ``True``.
+            in output visualization. Default is ``True`` unless the
+            user config file (usually ``~/.qiskit/settings.conf``) has an
+            alternative value set. For example, ``circuit_idle_wires = False``.
         with_layout: Include layout information, with labels on the
             physical layout. Default is ``True``.
         fold: Sets pagination. It can be disabled using -1. In ``text``,
@@ -200,6 +202,7 @@ def circuit_drawer(
     # Get default from config file else use text
     default_output = "text"
     default_reverse_bits = False
+    default_idle_wires = config.get("circuit_idle_wires", True)
     if config:
         default_output = config.get("circuit_drawer", "text")
         if default_output == "auto":
@@ -215,6 +218,9 @@ def circuit_drawer(
     if reverse_bits is None:
         reverse_bits = default_reverse_bits
 
+    if idle_wires is None:
+        idle_wires = default_idle_wires
+
     if wire_order is not None and reverse_bits:
         raise VisualizationError(
             "The wire_order option cannot be set when the reverse_bits option is True."
diff --git a/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml b/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml
new file mode 100644
index 000000000000..9c19be117ed2
--- /dev/null
+++ b/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml
@@ -0,0 +1,14 @@
+---
+features_visualization:
+  - |
+    The user configuration file has a new option ``circuit_idle_wires``, which takes a Boolean
+    value. This allows users to set their preferred default behavior of the ``idle_wires`` option
+    of the circuit drawers :meth:`.QuantumCircuit.draw` and :func:`.circuit_drawer`.  For example,
+    adding a section to ``~/.qiskit/settings.conf`` with:
+
+    .. code-block:: text
+    
+      [default]
+      circuit_idle_wires = false
+
+    will change the default to display the bits in reverse order.
diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py
index e3e012134639..ecc4ffaaa966 100644
--- a/test/python/test_user_config.py
+++ b/test/python/test_user_config.py
@@ -94,6 +94,31 @@ def test_circuit_reverse_bits_valid(self):
             config.read_config_file()
             self.assertEqual({"circuit_reverse_bits": False}, config.settings)
 
+    def test_invalid_circuit_idle_wires(self):
+        test_config = """
+        [default]
+        circuit_idle_wires = Neither
+        """
+        self.addCleanup(os.remove, self.file_path)
+        with open(self.file_path, "w") as file:
+            file.write(test_config)
+            file.flush()
+            config = user_config.UserConfig(self.file_path)
+            self.assertRaises(exceptions.QiskitUserConfigError, config.read_config_file)
+
+    def test_circuit_idle_wires_valid(self):
+        test_config = """
+        [default]
+        circuit_idle_wires = true
+        """
+        self.addCleanup(os.remove, self.file_path)
+        with open(self.file_path, "w") as file:
+            file.write(test_config)
+            file.flush()
+            config = user_config.UserConfig(self.file_path)
+            config.read_config_file()
+            self.assertEqual({"circuit_idle_wires": True}, config.settings)
+
     def test_optimization_level_valid(self):
         test_config = """
         [default]
@@ -152,6 +177,7 @@ def test_all_options_valid(self):
         circuit_mpl_style = default
         circuit_mpl_style_path = ~:~/.qiskit
         circuit_reverse_bits = false
+        circuit_idle_wires = true
         transpile_optimization_level = 3
         suppress_packaging_warnings = true
         parallel = false
@@ -170,6 +196,7 @@ def test_all_options_valid(self):
                 "circuit_mpl_style": "default",
                 "circuit_mpl_style_path": ["~", "~/.qiskit"],
                 "circuit_reverse_bits": False,
+                "circuit_idle_wires": True,
                 "transpile_optimization_level": 3,
                 "num_processes": 15,
                 "parallel_enabled": False,
@@ -184,6 +211,7 @@ def test_set_config_all_options_valid(self):
         user_config.set_config("circuit_mpl_style", "default", file_path=self.file_path)
         user_config.set_config("circuit_mpl_style_path", "~:~/.qiskit", file_path=self.file_path)
         user_config.set_config("circuit_reverse_bits", "false", file_path=self.file_path)
+        user_config.set_config("circuit_idle_wires", "true", file_path=self.file_path)
         user_config.set_config("transpile_optimization_level", "3", file_path=self.file_path)
         user_config.set_config("parallel", "false", file_path=self.file_path)
         user_config.set_config("num_processes", "15", file_path=self.file_path)
@@ -198,6 +226,7 @@ def test_set_config_all_options_valid(self):
                 "circuit_mpl_style": "default",
                 "circuit_mpl_style_path": ["~", "~/.qiskit"],
                 "circuit_reverse_bits": False,
+                "circuit_idle_wires": True,
                 "transpile_optimization_level": 3,
                 "num_processes": 15,
                 "parallel_enabled": False,
diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py
index 24e5e68ba987..fc933cd8f666 100644
--- a/test/python/transpiler/test_basis_translator.py
+++ b/test/python/transpiler/test_basis_translator.py
@@ -1150,15 +1150,16 @@ def setUp(self):
         self.target.add_instruction(CXGate(), cx_props)
 
     def test_2q_with_non_global_1q(self):
-        """Test translation works with a 2q gate on an non-global 1q basis."""
+        """Test translation works with a 2q gate on a non-global 1q basis."""
         qc = QuantumCircuit(2)
         qc.cz(0, 1)
 
         bt_pass = BasisTranslator(std_eqlib, target_basis=None, target=self.target)
         output = bt_pass(qc)
-        # We need a second run of BasisTranslator to correct gates outside of
-        # the target basis. This is a known isssue, see:
-        #  https://docs.quantum.ibm.com/api/qiskit/release-notes/0.33#known-issues
+        # We need a second run of BasisTranslator to correct gates outside
+        # the target basis. This is a known issue, see:
+        # https://github.com/Qiskit/qiskit/issues/11339
+        # TODO: remove the second bt_pass call once fixed.
         output = bt_pass(output)
         expected = QuantumCircuit(2)
         expected.rz(pi, 1)
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
index 5f72a7d1bbbc..3f018c085109 100644
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -495,6 +495,58 @@ def test_text_reverse_bits_read_from_config(self):
                 test_reverse = str(circuit_drawer(circuit, output="text"))
         self.assertEqual(test_reverse, expected_reverse)
 
+    def test_text_idle_wires_read_from_config(self):
+        """Swap drawing with idle_wires set in the configuration file."""
+        expected_with = "\n".join(
+            [
+                "      ┌───┐",
+                "q1_0: ┤ H ├",
+                "      └───┘",
+                "q1_1: ─────",
+                "      ┌───┐",
+                "q2_0: ┤ H ├",
+                "      └───┘",
+                "q2_1: ─────",
+                "           ",
+            ]
+        )
+        expected_without = "\n".join(
+            [
+                "      ┌───┐",
+                "q1_0: ┤ H ├",
+                "      ├───┤",
+                "q2_0: ┤ H ├",
+                "      └───┘",
+            ]
+        )
+        qr1 = QuantumRegister(2, "q1")
+        qr2 = QuantumRegister(2, "q2")
+        circuit = QuantumCircuit(qr1, qr2)
+        circuit.h(qr1[0])
+        circuit.h(qr2[0])
+
+        self.assertEqual(
+            str(
+                circuit_drawer(
+                    circuit,
+                    output="text",
+                )
+            ),
+            expected_with,
+        )
+
+        config_content = """
+            [default]
+            circuit_idle_wires = false
+        """
+        with tempfile.TemporaryDirectory() as dir_path:
+            file_path = pathlib.Path(dir_path) / "qiskit.conf"
+            with open(file_path, "w") as fptr:
+                fptr.write(config_content)
+            with unittest.mock.patch.dict(os.environ, {"QISKIT_SETTINGS": str(file_path)}):
+                test_without = str(circuit_drawer(circuit, output="text"))
+        self.assertEqual(test_without, expected_without)
+
     def test_text_cswap(self):
         """CSwap drawing."""
         expected = "\n".join(
@@ -514,6 +566,7 @@ def test_text_cswap(self):
         circuit.cswap(qr[0], qr[1], qr[2])
         circuit.cswap(qr[1], qr[0], qr[2])
         circuit.cswap(qr[2], qr[1], qr[0])
+
         self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected)
 
     def test_text_cswap_reverse_bits(self):
@@ -4223,7 +4276,6 @@ def test_text_4q_2c(self):
         cr6 = ClassicalRegister(6, "c")
         circuit = QuantumCircuit(qr6, cr6)
         circuit.append(inst, qr6[1:5], cr6[1:3])
-
         self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected)
 
     def test_text_2q_1c(self):
@@ -5668,7 +5720,6 @@ def test_registerless_one_bit(self):
         qry = QuantumRegister(1, "qry")
         crx = ClassicalRegister(2, "crx")
         circuit = QuantumCircuit(qrx, [Qubit(), Qubit()], qry, [Clbit(), Clbit()], crx)
-
         self.assertEqual(circuit.draw(output="text", cregbundle=True).single_string(), expected)
 
 

From 287e86ce8a6ca3412747c31a5f76fe61d85952f6 Mon Sep 17 00:00:00 2001
From: Jim Garrison 
Date: Wed, 12 Jun 2024 05:32:36 -0400
Subject: [PATCH 142/179] Use relative import for `_accelerate` (#12546)

* Use relative import for `_accelerate`

* Fix black

* Disable wrong-import-order in `__init__.py`
---
 qiskit/__init__.py | 46 ++++++++++++++++++++++------------------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/qiskit/__init__.py b/qiskit/__init__.py
index 27126de6df66..fce544333478 100644
--- a/qiskit/__init__.py
+++ b/qiskit/__init__.py
@@ -10,7 +10,7 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-# pylint: disable=wrong-import-position
+# pylint: disable=wrong-import-position,wrong-import-order
 
 """Main Qiskit public functionality."""
 
@@ -52,37 +52,35 @@
     )
 
 
-import qiskit._accelerate
+from . import _accelerate
 import qiskit._numpy_compat
 
 # Globally define compiled submodules. The normal import mechanism will not find compiled submodules
 # in _accelerate because it relies on file paths, but PyO3 generates only one shared library file.
 # We manually define them on import so people can directly import qiskit._accelerate.* submodules
 # and not have to rely on attribute access.  No action needed for top-level extension packages.
-sys.modules["qiskit._accelerate.circuit"] = qiskit._accelerate.circuit
-sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = (
-    qiskit._accelerate.convert_2q_block_matrix
-)
-sys.modules["qiskit._accelerate.dense_layout"] = qiskit._accelerate.dense_layout
-sys.modules["qiskit._accelerate.error_map"] = qiskit._accelerate.error_map
-sys.modules["qiskit._accelerate.isometry"] = qiskit._accelerate.isometry
-sys.modules["qiskit._accelerate.uc_gate"] = qiskit._accelerate.uc_gate
+sys.modules["qiskit._accelerate.circuit"] = _accelerate.circuit
+sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix
+sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout
+sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map
+sys.modules["qiskit._accelerate.isometry"] = _accelerate.isometry
+sys.modules["qiskit._accelerate.uc_gate"] = _accelerate.uc_gate
 sys.modules["qiskit._accelerate.euler_one_qubit_decomposer"] = (
-    qiskit._accelerate.euler_one_qubit_decomposer
+    _accelerate.euler_one_qubit_decomposer
 )
-sys.modules["qiskit._accelerate.nlayout"] = qiskit._accelerate.nlayout
-sys.modules["qiskit._accelerate.optimize_1q_gates"] = qiskit._accelerate.optimize_1q_gates
-sys.modules["qiskit._accelerate.pauli_expval"] = qiskit._accelerate.pauli_expval
-sys.modules["qiskit._accelerate.qasm2"] = qiskit._accelerate.qasm2
-sys.modules["qiskit._accelerate.qasm3"] = qiskit._accelerate.qasm3
-sys.modules["qiskit._accelerate.results"] = qiskit._accelerate.results
-sys.modules["qiskit._accelerate.sabre"] = qiskit._accelerate.sabre
-sys.modules["qiskit._accelerate.sampled_exp_val"] = qiskit._accelerate.sampled_exp_val
-sys.modules["qiskit._accelerate.sparse_pauli_op"] = qiskit._accelerate.sparse_pauli_op
-sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap
-sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose
-sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout
-sys.modules["qiskit._accelerate.permutation"] = qiskit._accelerate.permutation
+sys.modules["qiskit._accelerate.nlayout"] = _accelerate.nlayout
+sys.modules["qiskit._accelerate.optimize_1q_gates"] = _accelerate.optimize_1q_gates
+sys.modules["qiskit._accelerate.pauli_expval"] = _accelerate.pauli_expval
+sys.modules["qiskit._accelerate.qasm2"] = _accelerate.qasm2
+sys.modules["qiskit._accelerate.qasm3"] = _accelerate.qasm3
+sys.modules["qiskit._accelerate.results"] = _accelerate.results
+sys.modules["qiskit._accelerate.sabre"] = _accelerate.sabre
+sys.modules["qiskit._accelerate.sampled_exp_val"] = _accelerate.sampled_exp_val
+sys.modules["qiskit._accelerate.sparse_pauli_op"] = _accelerate.sparse_pauli_op
+sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap
+sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose
+sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout
+sys.modules["qiskit._accelerate.permutation"] = _accelerate.permutation
 
 from qiskit.exceptions import QiskitError, MissingOptionalLibraryError
 

From 8d2144cec32d5f20e03a8c9af1999a0b7b7a1645 Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Wed, 12 Jun 2024 15:02:12 +0200
Subject: [PATCH 143/179] small doc improvements (#12553)

* small doc improvements

* Update qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py

Co-authored-by: Jake Lishman 

---------

Co-authored-by: Jake Lishman 
---
 qiskit/dagcircuit/dagcircuit.py                             | 2 +-
 qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py | 2 +-
 qiskit/transpiler/preset_passmanagers/__init__.py           | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index 831851bee36d..96562a37b151 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -544,7 +544,7 @@ def remove_qubits(self, *qubits):
 
     def remove_qregs(self, *qregs):
         """
-        Remove classical registers from the circuit, leaving underlying bits
+        Remove quantum registers from the circuit, leaving underlying bits
         in place.
 
         Raises:
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index 440a0319c33a..f8d25a6e8daa 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -54,7 +54,7 @@ class SparsePauliOp(LinearOp):
     :class:`~qiskit.quantum_info.Operator` in terms of N-qubit
     :class:`~qiskit.quantum_info.PauliList` and complex coefficients.
 
-    It can be used for performing operator arithmetic for hundred of qubits
+    It can be used for performing operator arithmetic for hundreds of qubits
     if the number of non-zero Pauli basis terms is sufficiently small.
 
     The Pauli basis components are stored as a
diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py
index 37b284a4680f..f2f011e486c4 100644
--- a/qiskit/transpiler/preset_passmanagers/__init__.py
+++ b/qiskit/transpiler/preset_passmanagers/__init__.py
@@ -109,12 +109,12 @@ def generate_preset_pass_manager(
     This function is used to quickly generate a preset pass manager. Preset pass
     managers are the default pass managers used by the :func:`~.transpile`
     function. This function provides a convenient and simple method to construct
-    a standalone :class:`~.PassManager` object that mirrors what the transpile
+    a standalone :class:`~.PassManager` object that mirrors what the :func:`~.transpile`
     function internally builds and uses.
 
     The target constraints for the pass manager construction can be specified through a :class:`.Target`
-    instance, a `.BackendV1` or `.BackendV2` instance, or via loose constraints (``basis_gates``,
-    ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``,
+    instance, a :class:`.BackendV1` or :class:`.BackendV2` instance, or via loose constraints
+    (``basis_gates``, ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``,
     ``dt`` or ``timing_constraints``).
     The order of priorities for target constraints works as follows: if a ``target``
     input is provided, it will take priority over any ``backend`` input or loose constraints.

From 8a1bcc2d5236ac090cd9bcbd7cdf5ce8efbcb18c Mon Sep 17 00:00:00 2001
From: Jim Garrison 
Date: Thu, 13 Jun 2024 04:18:43 -0400
Subject: [PATCH 144/179] Do not retain and expose old elements of
 `ParameterVector` (#12561)

* Do not retain and expose old elements of `ParameterVector`

This fixes #12541 according to
https://github.com/Qiskit/qiskit/pull/12545#pullrequestreview-2114202382.

* Fix test
---
 qiskit/circuit/parametervector.py      | 38 +++++++++++++++-----------
 qiskit/qpy/binary_io/value.py          |  2 +-
 test/python/circuit/test_parameters.py |  6 ++--
 3 files changed, 25 insertions(+), 21 deletions(-)

diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py
index abc8a6f60ef7..151e3e7fea73 100644
--- a/qiskit/circuit/parametervector.py
+++ b/qiskit/circuit/parametervector.py
@@ -50,11 +50,10 @@ def __setstate__(self, state):
 class ParameterVector:
     """ParameterVector class to quickly generate lists of parameters."""
 
-    __slots__ = ("_name", "_params", "_size", "_root_uuid")
+    __slots__ = ("_name", "_params", "_root_uuid")
 
     def __init__(self, name, length=0):
         self._name = name
-        self._size = length
         self._root_uuid = uuid4()
         root_uuid_int = self._root_uuid.int
         self._params = [
@@ -76,32 +75,38 @@ def index(self, value):
         return self._params.index(value)
 
     def __getitem__(self, key):
-        if isinstance(key, slice):
-            start, stop, step = key.indices(self._size)
-            return self.params[start:stop:step]
-
-        if key > self._size:
-            raise IndexError(f"Index out of range: {key} > {self._size}")
         return self.params[key]
 
     def __iter__(self):
-        return iter(self.params[: self._size])
+        return iter(self.params)
 
     def __len__(self):
-        return self._size
+        return len(self._params)
 
     def __str__(self):
-        return f"{self.name}, {[str(item) for item in self.params[: self._size]]}"
+        return f"{self.name}, {[str(item) for item in self.params]}"
 
     def __repr__(self):
         return f"{self.__class__.__name__}(name={self.name}, length={len(self)})"
 
     def resize(self, length):
-        """Resize the parameter vector.
-
-        If necessary, new elements are generated. If length is smaller than before, the
-        previous elements are cached and not re-generated if the vector is enlarged again.
+        """Resize the parameter vector.  If necessary, new elements are generated.
+
+        Note that the UUID of each :class:`.Parameter` element will be generated
+        deterministically given the root UUID of the ``ParameterVector`` and the index
+        of the element.  In particular, if a ``ParameterVector`` is resized to
+        be smaller and then later resized to be larger, the UUID of the later
+        generated element at a given index will be the same as the UUID of the
+        previous element at that index.
         This is to ensure that the parameter instances do not change.
+
+        >>> from qiskit.circuit import ParameterVector
+        >>> pv = ParameterVector("theta", 20)
+        >>> elt_19 = pv[19]
+        >>> rv.resize(10)
+        >>> rv.resize(20)
+        >>> pv[19] == elt_19
+        True
         """
         if length > len(self._params):
             root_uuid_int = self._root_uuid.int
@@ -111,4 +116,5 @@ def resize(self, length):
                     for i in range(len(self._params), length)
                 ]
             )
-        self._size = length
+        else:
+            del self._params[length:]
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
index fdad363867a3..c9f0f9af4798 100644
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -45,7 +45,7 @@ def _write_parameter_vec(file_obj, obj):
         struct.pack(
             formats.PARAMETER_VECTOR_ELEMENT_PACK,
             len(name_bytes),
-            obj._vector._size,
+            len(obj._vector),
             obj.uuid.bytes,
             obj._index,
         )
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
index c9380fd07689..7cdc4ed56ab6 100644
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -1368,10 +1368,8 @@ def test_parametervector_resize(self):
         with self.subTest("enlargen"):
             vec.resize(3)
             self.assertEqual(len(vec), 3)
-            # ensure we still have the same instance not a copy with the same name
-            # this is crucial for adding parameters to circuits since we cannot use the same
-            # name if the instance is not the same
-            self.assertIs(element, vec[1])
+            # ensure we still have an element with the same uuid
+            self.assertEqual(element, vec[1])
             self.assertListEqual([param.name for param in vec], _paramvec_names("x", 3))
 
     def test_raise_if_sub_unknown_parameters(self):

From 439de04e88546f632ac9a76fa4b6bff03da529f1 Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Thu, 13 Jun 2024 12:47:34 +0200
Subject: [PATCH 145/179] Fix the possibly-used-before-assignment in pylint
 (#12542)

* fix the possibly-used-before-assignment in pylint

* qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py

* test.python.quantum_info.operators.test_utils

* Apply suggestions from code review

Co-authored-by: Jake Lishman 

* https://github.com/Qiskit/qiskit/pull/12542/files#r1636214044

* RuntimeError

* RuntimeError

---------

Co-authored-by: Jake Lishman 
---
 pyproject.toml                                |  1 -
 qiskit/primitives/backend_estimator.py        |  2 ++
 qiskit/pulse/macros.py                        |  9 +++++++--
 qiskit/qpy/binary_io/circuits.py              |  1 +
 qiskit/quantum_info/operators/operator.py     | 20 +++++++++----------
 .../operators/symplectic/sparse_pauli_op.py   | 20 +++++++++----------
 .../passes/basis/unroll_custom_definitions.py | 14 ++++++-------
 .../optimization/commutative_cancellation.py  | 12 ++++++-----
 qiskit/visualization/circuit/matplotlib.py    |  2 ++
 test/benchmarks/randomized_benchmarking.py    |  1 +
 test/benchmarks/utils.py                      |  2 ++
 test/python/circuit/test_parameters.py        |  7 ++++---
 .../test_boolean_expression.py                |  1 +
 .../test_classical_function.py                |  1 +
 .../classical_function_compiler/test_parse.py |  1 +
 .../test_simulate.py                          |  1 +
 .../test_synthesis.py                         |  1 +
 .../test_tweedledum2qiskit.py                 |  1 +
 .../test_typecheck.py                         |  2 ++
 .../visualization/test_circuit_drawer.py      |  2 ++
 .../visualization/test_circuit_text_drawer.py |  1 +
 test/python/visualization/test_gate_map.py    |  1 +
 .../visualization/test_plot_histogram.py      |  1 +
 tools/build_standard_commutations.py          |  4 ++--
 24 files changed, 67 insertions(+), 41 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index c13486c21bb3..149d6c0f2d47 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -221,7 +221,6 @@ disable = [
     "consider-using-f-string",
     "no-member",  # for dynamically created members
     "not-context-manager",
-    "possibly-used-before-assignment",
     "unnecessary-lambda-assignment",  # do not want to implement
     "unspecified-encoding",  # do not want to implement
 ]
diff --git a/qiskit/primitives/backend_estimator.py b/qiskit/primitives/backend_estimator.py
index b91ea7068be1..8446c870b1fd 100644
--- a/qiskit/primitives/backend_estimator.py
+++ b/qiskit/primitives/backend_estimator.py
@@ -65,6 +65,8 @@ def _run_circuits(
         max_circuits = getattr(backend.configuration(), "max_experiments", None)
     elif isinstance(backend, BackendV2):
         max_circuits = backend.max_circuits
+    else:
+        raise RuntimeError("Backend version not supported")
     if max_circuits:
         jobs = [
             backend.run(circuits[pos : pos + max_circuits], **run_options)
diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py
index 1995d6d20c40..88414cfc7e9b 100644
--- a/qiskit/pulse/macros.py
+++ b/qiskit/pulse/macros.py
@@ -124,8 +124,13 @@ def _measure_v1(
     for qubit in qubits:
         measure_groups.add(tuple(meas_map[qubit]))
     for measure_group_qubits in measure_groups:
-        if qubit_mem_slots is not None:
-            unused_mem_slots = set(measure_group_qubits) - set(qubit_mem_slots.values())
+
+        unused_mem_slots = (
+            set()
+            if qubit_mem_slots is None
+            else set(measure_group_qubits) - set(qubit_mem_slots.values())
+        )
+
         try:
             default_sched = inst_map.get(measure_name, measure_group_qubits)
         except exceptions.PulseError as ex:
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
index 25103e7b4c27..db53defbcfa7 100644
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -446,6 +446,7 @@ def _parse_custom_operation(
         ) = custom_operations[gate_name]
     else:
         type_str, num_qubits, num_clbits, definition = custom_operations[gate_name]
+        base_gate_raw = ctrl_state = num_ctrl_qubits = None
     # Strip the trailing "_{uuid}" from the gate name if the version >=11
     if version >= 11:
         gate_name = "_".join(gate_name.split("_")[:-1])
diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
index 41eac3563576..016e337f082c 100644
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -414,6 +414,8 @@ def from_circuit(
 
         from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern
 
+        op = Operator(circuit)
+
         if initial_layout is not None:
             input_qubits = [None] * len(layout.input_qubit_mapping)
             for q, p in layout.input_qubit_mapping.items():
@@ -421,22 +423,18 @@ def from_circuit(
 
             initial_permutation = initial_layout.to_permutation(input_qubits)
             initial_permutation_inverse = _inverse_pattern(initial_permutation)
+            op = op.apply_permutation(initial_permutation, True)
 
-        if final_layout is not None:
+            if final_layout is not None:
+                final_permutation = final_layout.to_permutation(circuit.qubits)
+                final_permutation_inverse = _inverse_pattern(final_permutation)
+                op = op.apply_permutation(final_permutation_inverse, False)
+            op = op.apply_permutation(initial_permutation_inverse, False)
+        elif final_layout is not None:
             final_permutation = final_layout.to_permutation(circuit.qubits)
             final_permutation_inverse = _inverse_pattern(final_permutation)
-
-        op = Operator(circuit)
-
-        if initial_layout:
-            op = op.apply_permutation(initial_permutation, True)
-
-        if final_layout:
             op = op.apply_permutation(final_permutation_inverse, False)
 
-        if initial_layout:
-            op = op.apply_permutation(initial_permutation_inverse, False)
-
         return op
 
     def is_unitary(self, atol=None, rtol=None):
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index f8d25a6e8daa..6204189be443 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -135,19 +135,19 @@ def __init__(
 
         pauli_list = PauliList(data.copy() if copy and hasattr(data, "copy") else data)
 
-        if isinstance(coeffs, np.ndarray):
-            dtype = object if coeffs.dtype == object else complex
-        elif coeffs is not None:
-            if not isinstance(coeffs, (np.ndarray, Sequence)):
-                coeffs = [coeffs]
-            if any(isinstance(coeff, ParameterExpression) for coeff in coeffs):
-                dtype = object
-            else:
-                dtype = complex
-
         if coeffs is None:
             coeffs = np.ones(pauli_list.size, dtype=complex)
         else:
+            if isinstance(coeffs, np.ndarray):
+                dtype = object if coeffs.dtype == object else complex
+            else:
+                if not isinstance(coeffs, Sequence):
+                    coeffs = [coeffs]
+                if any(isinstance(coeff, ParameterExpression) for coeff in coeffs):
+                    dtype = object
+                else:
+                    dtype = complex
+
             coeffs_asarray = np.asarray(coeffs, dtype=dtype)
             coeffs = (
                 coeffs_asarray.copy()
diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
index 2a95f540f886..a54e4bfcb001 100644
--- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
+++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
@@ -60,9 +60,9 @@ def run(self, dag):
         if self._basis_gates is None and self._target is None:
             return dag
 
+        device_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
         if self._target is None:
-            basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
-            device_insts = basic_insts | set(self._basis_gates)
+            device_insts |= set(self._basis_gates)
 
         for node in dag.op_nodes():
             if isinstance(node.op, ControlFlowOp):
@@ -77,14 +77,14 @@ def run(self, dag):
 
             controlled_gate_open_ctrl = isinstance(node.op, ControlledGate) and node.op._open_ctrl
             if not controlled_gate_open_ctrl:
-                inst_supported = (
-                    self._target.instruction_supported(
+                if self._target is not None:
+                    inst_supported = self._target.instruction_supported(
                         operation_name=node.op.name,
                         qargs=tuple(dag.find_bit(x).index for x in node.qargs),
                     )
-                    if self._target is not None
-                    else node.name in device_insts
-                )
+                else:
+                    inst_supported = node.name in device_insts
+
                 if inst_supported or self._equiv_lib.has_entry(node.op):
                     continue
             try:
diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py
index 396186fa95cc..68d40f3650a2 100644
--- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py
+++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py
@@ -16,7 +16,6 @@
 import numpy as np
 
 from qiskit.circuit.quantumregister import QuantumRegister
-from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.transpiler.passmanager import PassManager
 from qiskit.transpiler.passes.optimization.commutation_analysis import CommutationAnalysis
@@ -72,9 +71,6 @@ def run(self, dag):
 
         Returns:
             DAGCircuit: the optimized DAG.
-
-        Raises:
-            TranspilerError: when the 1-qubit rotation gates are not found
         """
         var_z_gate = None
         z_var_gates = [gate for gate in dag.count_ops().keys() if gate in self._var_z_map]
@@ -146,7 +142,7 @@ def run(self, dag):
                         or len(current_node.qargs) != 1
                         or current_node.qargs[0] != run_qarg
                     ):
-                        raise TranspilerError("internal error")
+                        raise RuntimeError("internal error")
 
                     if current_node.name in ["p", "u1", "rz", "rx"]:
                         current_angle = float(current_node.op.params[0])
@@ -156,6 +152,10 @@ def run(self, dag):
                         current_angle = np.pi / 4
                     elif current_node.name == "s":
                         current_angle = np.pi / 2
+                    else:
+                        raise RuntimeError(
+                            f"Angle for operation {current_node.name } is not defined"
+                        )
 
                     # Compose gates
                     total_angle = current_angle + total_angle
@@ -167,6 +167,8 @@ def run(self, dag):
                     new_op = var_z_gate(total_angle)
                 elif cancel_set_key[0] == "x_rotation":
                     new_op = RXGate(total_angle)
+                else:
+                    raise RuntimeError("impossible case")
 
                 new_op_phase = 0
                 if np.mod(total_angle, (2 * np.pi)) > _CUTOFF_PRECISION:
diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py
index b4252065006c..0076073fb8e4 100644
--- a/qiskit/visualization/circuit/matplotlib.py
+++ b/qiskit/visualization/circuit/matplotlib.py
@@ -1584,6 +1584,8 @@ def _flow_op_gate(self, node, node_data, glob_data):
                 flow_text = " For"
             elif isinstance(node.op, SwitchCaseOp):
                 flow_text = "Switch"
+            else:
+                flow_text = node.op.name
 
             # Some spacers. op_spacer moves 'Switch' back a bit for alignment,
             # expr_spacer moves the expr over to line up with 'Switch' and
diff --git a/test/benchmarks/randomized_benchmarking.py b/test/benchmarks/randomized_benchmarking.py
index f3c3d18e9f96..9847c928ad71 100644
--- a/test/benchmarks/randomized_benchmarking.py
+++ b/test/benchmarks/randomized_benchmarking.py
@@ -105,6 +105,7 @@ def clifford_2_qubit_circuit(num):
     qc = QuantumCircuit(2)
     if vals[0] == 0 or vals[0] == 3:
         (form, i0, i1, j0, j1, p0, p1) = vals
+        k0, k1 = (None, None)
     else:
         (form, i0, i1, j0, j1, k0, k1, p0, p1) = vals
     if i0 == 1:
diff --git a/test/benchmarks/utils.py b/test/benchmarks/utils.py
index d932e8d6a0c5..bbd7d0a9af85 100644
--- a/test/benchmarks/utils.py
+++ b/test/benchmarks/utils.py
@@ -126,6 +126,8 @@ def random_circuit(
                 operation = rng.choice(two_q_ops)
             elif num_operands == 3:
                 operation = rng.choice(three_q_ops)
+            else:
+                raise RuntimeError("not supported number of operands")
             if operation in one_param:
                 num_angles = 1
             elif operation in two_param:
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
index 7cdc4ed56ab6..c86deee42876 100644
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -1091,7 +1091,7 @@ def test_decompose_propagates_bound_parameters(self, target_type, parameter_type
 
         if target_type == "gate":
             inst = qc.to_gate()
-        elif target_type == "instruction":
+        else:  # target_type == "instruction":
             inst = qc.to_instruction()
 
         qc2 = QuantumCircuit(1)
@@ -1132,7 +1132,7 @@ def test_decompose_propagates_deeply_bound_parameters(self, target_type, paramet
 
         if target_type == "gate":
             inst = qc1.to_gate()
-        elif target_type == "instruction":
+        else:  # target_type == "instruction":
             inst = qc1.to_instruction()
 
         qc2 = QuantumCircuit(1)
@@ -1188,7 +1188,7 @@ def test_executing_parameterized_instruction_bound_early(self, target_type):
 
         if target_type == "gate":
             sub_inst = sub_qc.to_gate()
-        elif target_type == "instruction":
+        else:  # target_type == "instruction":
             sub_inst = sub_qc.to_instruction()
 
         unbound_qc = QuantumCircuit(2, 1)
@@ -1405,6 +1405,7 @@ def _paramvec_names(prefix, length):
 
 @ddt
 class TestParameterExpressions(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Test expressions of Parameters."""
 
     # supported operations dictionary operation : accuracy (0=exact match)
diff --git a/test/python/classical_function_compiler/test_boolean_expression.py b/test/python/classical_function_compiler/test_boolean_expression.py
index afdc91fdd04f..40a01b154c63 100644
--- a/test/python/classical_function_compiler/test_boolean_expression.py
+++ b/test/python/classical_function_compiler/test_boolean_expression.py
@@ -28,6 +28,7 @@
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 @ddt
 class TestBooleanExpression(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Test boolean expression."""
 
     @data(
diff --git a/test/python/classical_function_compiler/test_classical_function.py b/test/python/classical_function_compiler/test_classical_function.py
index d4a0bf66d493..e385745952e9 100644
--- a/test/python/classical_function_compiler/test_classical_function.py
+++ b/test/python/classical_function_compiler/test_classical_function.py
@@ -26,6 +26,7 @@
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestOracleDecomposition(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests ClassicalFunction.decomposition."""
 
     def test_grover_oracle(self):
diff --git a/test/python/classical_function_compiler/test_parse.py b/test/python/classical_function_compiler/test_parse.py
index 15862ca71b3a..9da93873c700 100644
--- a/test/python/classical_function_compiler/test_parse.py
+++ b/test/python/classical_function_compiler/test_parse.py
@@ -25,6 +25,7 @@
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestParseFail(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests bad_examples with the classicalfunction parser."""
 
     def assertExceptionMessage(self, context, message):
diff --git a/test/python/classical_function_compiler/test_simulate.py b/test/python/classical_function_compiler/test_simulate.py
index 65399de82d08..f7c6ef3dd165 100644
--- a/test/python/classical_function_compiler/test_simulate.py
+++ b/test/python/classical_function_compiler/test_simulate.py
@@ -26,6 +26,7 @@
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 @ddt
 class TestSimulate(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests LogicNetwork.simulate method"""
 
     @data(*utils.example_list())
diff --git a/test/python/classical_function_compiler/test_synthesis.py b/test/python/classical_function_compiler/test_synthesis.py
index 3b8890d986ce..1d44b58882f8 100644
--- a/test/python/classical_function_compiler/test_synthesis.py
+++ b/test/python/classical_function_compiler/test_synthesis.py
@@ -26,6 +26,7 @@
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestSynthesis(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests ClassicalFunction.synth method."""
 
     def test_grover_oracle(self):
diff --git a/test/python/classical_function_compiler/test_tweedledum2qiskit.py b/test/python/classical_function_compiler/test_tweedledum2qiskit.py
index 32bd9485fdc2..ff9b73a5b551 100644
--- a/test/python/classical_function_compiler/test_tweedledum2qiskit.py
+++ b/test/python/classical_function_compiler/test_tweedledum2qiskit.py
@@ -29,6 +29,7 @@
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestTweedledum2Qiskit(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests qiskit.transpiler.classicalfunction.utils.tweedledum2qiskit function."""
 
     def test_x(self):
diff --git a/test/python/classical_function_compiler/test_typecheck.py b/test/python/classical_function_compiler/test_typecheck.py
index 36b64ce4fd4d..ffe57cc3d4b3 100644
--- a/test/python/classical_function_compiler/test_typecheck.py
+++ b/test/python/classical_function_compiler/test_typecheck.py
@@ -25,6 +25,7 @@
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestTypeCheck(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests classicalfunction compiler type checker (good examples)."""
 
     def test_id(self):
@@ -74,6 +75,7 @@ def test_bool_or(self):
 
 @unittest.skipUnless(HAS_TWEEDLEDUM, "Tweedledum is required for these tests.")
 class TestTypeCheckFail(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Tests classicalfunction compiler type checker (bad examples)."""
 
     def assertExceptionMessage(self, context, message):
diff --git a/test/python/visualization/test_circuit_drawer.py b/test/python/visualization/test_circuit_drawer.py
index f02f1ad11431..e6b430c4ee82 100644
--- a/test/python/visualization/test_circuit_drawer.py
+++ b/test/python/visualization/test_circuit_drawer.py
@@ -55,6 +55,7 @@ def test_default_output(self):
 
     @unittest.skipUnless(optionals.HAS_MATPLOTLIB, "Skipped because matplotlib is not available")
     def test_mpl_config_with_path(self):
+        # pylint: disable=possibly-used-before-assignment
         # It's too easy to get too nested in a test with many context managers.
         tempdir = tempfile.TemporaryDirectory()  # pylint: disable=consider-using-with
         self.addCleanup(tempdir.cleanup)
@@ -128,6 +129,7 @@ def test_latex_unsupported_image_format_error_message(self):
 
     @_latex_drawer_condition
     def test_latex_output_file_correct_format(self):
+        # pylint: disable=possibly-used-before-assignment
         with patch("qiskit.user_config.get_config", return_value={"circuit_drawer": "latex"}):
             circuit = QuantumCircuit()
             filename = "file.gif"
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
index 3f018c085109..2a0a61c7904b 100644
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -185,6 +185,7 @@ def test_text_no_pager(self):
 
 
 class TestTextDrawerGatesInCircuit(QiskitTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Gate by gate checks in different settings."""
 
     def test_text_measure_cregbundle(self):
diff --git a/test/python/visualization/test_gate_map.py b/test/python/visualization/test_gate_map.py
index dd3a479ba65d..bf9b1ca80d79 100644
--- a/test/python/visualization/test_gate_map.py
+++ b/test/python/visualization/test_gate_map.py
@@ -45,6 +45,7 @@
 @unittest.skipUnless(optionals.HAS_PIL, "PIL not available")
 @unittest.skipUnless(optionals.HAS_SEABORN, "seaborn not available")
 class TestGateMap(QiskitVisualizationTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """visual tests for plot_gate_map"""
 
     backends = [Fake5QV1(), Fake20QV1(), Fake7QPulseV1()]
diff --git a/test/python/visualization/test_plot_histogram.py b/test/python/visualization/test_plot_histogram.py
index 7c530851326d..2668f3ff679d 100644
--- a/test/python/visualization/test_plot_histogram.py
+++ b/test/python/visualization/test_plot_histogram.py
@@ -28,6 +28,7 @@
 
 @unittest.skipUnless(optionals.HAS_MATPLOTLIB, "matplotlib not available.")
 class TestPlotHistogram(QiskitVisualizationTestCase):
+    # pylint: disable=possibly-used-before-assignment
     """Qiskit plot_histogram tests."""
 
     def test_different_counts_lengths(self):
diff --git a/tools/build_standard_commutations.py b/tools/build_standard_commutations.py
index 72798f0eb4be..56c452b11ce0 100644
--- a/tools/build_standard_commutations.py
+++ b/tools/build_standard_commutations.py
@@ -102,12 +102,12 @@ def _generate_commutation_dict(considered_gates: List[Gate] = None) -> dict:
                     commutation_relation = cc.commute(
                         op1, qargs1, cargs1, op2, qargs2, cargs2, max_num_qubits=4
                     )
+
+                    gate_pair_commutation[relative_placement] = commutation_relation
                 else:
                     pass
                     # TODO
 
-                gate_pair_commutation[relative_placement] = commutation_relation
-
             commutations[gate0.name, gate1.name] = gate_pair_commutation
     return commutations
 

From f304a4b4f8add43defafeabbbbd32baefa30aa7c Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Thu, 13 Jun 2024 06:48:40 -0400
Subject: [PATCH 146/179] Add infrastructure for gates, instruction, and
 operations in Rust (#12459)

* Add infrastructure for gates, instruction, and operations in Rust

This commit adds a native representation of Gates, Instruction, and
Operations to rust's circuit module. At a high level this works by
either wrapping the Python object in a rust wrapper struct that tracks
metadata about the operations (name, num_qubits, etc) and then for other
details it calls back to Python to get dynamic details like the
definition, matrix, etc. For standard library gates like Swap, CX, H,
etc this replaces the on-circuit representation with a new rust enum
StandardGate. The enum representation is much more efficient and has a
minimal memory footprint (just the enum variant and then any parameters
or other mutable state stored in the circuit instruction). All the gate
properties such as the matrix, definiton, name, etc are statically
defined in rust code based on the enum variant (which represents the
gate).

The use of an enum to represent standard gates does mean a change in
what we store on a CircuitInstruction. To represent a standard gate
fully we need to store the mutable properties of the existing Gate class
on the circuit instruction as the gate by itself doesn't contain this
detail. That means, the parameters, label, unit, duration, and condition
are added to the rust side of circuit instrucion. However no Python side
access methods are added for these as they're internal only to the Rust
code. In Qiskit 2.0 to simplify this storage we'll be able to drop, unit,
duration, and condition from the api leaving only label and parameters.
But for right now we're tracking all of the fields.

To facilitate working with circuits and gates full from rust the
setting the `operation` attribute of a `CircuitInstruction` object now
transltates the python object to an internal rust representation.
For standard gates this translates it to the enum form described earlier,
and for other circuit operations 3 new Rust structs: PyGate,
PyInstruction, and PyOperation are used to wrap the underlying Python
object in a Rust api. These structs cache some commonly accessed static
properties of the operation, such as the name, number of qubits, etc.
However for dynamic pieces, such as the definition or matrix, callback
to python to get a rust representation for those.

Similarly whenever the `operation` attribute is accessed from Python
it converts it back to the normal Python object representation. For
standard gates this involves creating a new instance of a Python object
based on it's internal rust representation. For the wrapper structs a
reference to the wrapped PyObject is returned.

To manage the 4 variants of operation (`StandardGate`, `PyGate`,
`PyInstruction`, and `PyOperation`) a new Rust trait `Operation` is
created that defines a standard interface for getting the properties
of a given circuit operation. This common interface is implemented for
the 4 variants as well as the `OperationType` enum which wraps all 4
(and is used as the type for `CircuitInstruction.operation` in the
rust code.

As everything in the `QuantumCircuit` data model is quite coupled moving
the source of truth for the operations to exist in Rust means that more
of the underlying `QuantumCircuit`'s responsibility has to move to Rust
as well. Primarily this involves the `ParameterTable` which was an
internal class for tracking which instructions in the circuit have a
`ParameterExpression` parameter so that when we go to bind parameters we
can lookup which operations need to be updated with the bind value.
Since the representation of those instructions now lives in Rust and
Python only recieves a ephemeral copy of the instructions the
ParameterTable had to be reimplemented in Rust to track the
instructions. This new parameter table maps the Parameter's uuid (as a
u128) as a unique identifier for each parameter and maps this to a
positional index in the circuit data to the underlying instruction using
that parameter. This is a bit different from the Python parameter table
which was mapping a parameter object to the id of the operation object
using that parmaeter. This also leads to a difference in the binding
mechanics as the parameter assignment was done by reference in the old
model, but now we need to update the entire instruction more explicitly
in rust. Additionally, because the global phase of a circuit can be
parameterized the ownership of global phase is moved from Python into
Rust in this commit as well.

After this commit the only properties of a circuit that are not defined
in Rust for the source of truth are the bits (and vars) of the circuit,
and when creating circuits from rust this is what causes a Python
interaction to still be required.

This commit does not translate the full standard library of gates as
that would make the pull request huge, instead this adds the basic
infrastructure for having a more efficient standard gate representation
on circuits. There will be follow up pull requests to add the missing
gates and round out support in rust.

The goal of this pull request is primarily to add the infrastructure for
representing the full circuit model (and dag model in the future) in
rust. By itself this is not expected to improve runtime performance (if
anything it will probably hurt performance because of extra type
conversions) but it is intended to enable writing native circuit
manipulations in Rust, including transpiler passes without needing
involvement from Python. Longer term this should greatly improve the
runtime performance and reduce the memory overhead of Qiskit. But,
this is just an early step towards that goal, and is more about
unlocking the future capability. The next steps after this commit are
to finish migrating the standard gate library and also update the
`QuantumCircuit` methods to better leverage the more complete rust
representation (which should help offset the performance penalty
introduced by this).

Fixes: #12205

* Fix Python->Rust Param conversion

This commit adds a custom implementation of the FromPyObject trait for
the Param enum. Previously, the Param trait derived it's impl of the
trait, but this logic wasn't perfect. In cases whern a
ParameterExpression was effectively a constant (such as `0 * x`) the
trait's attempt to coerce to a float first would result in those
ParameterExpressions being dropped from the circuit at insertion time.
This was a change in behavior from before having gates in Rust as the
parameters would disappear from the circuit at insertion time instead of
at bind time. This commit fixes this by having a custom impl for
FromPyObject that first tries to figure out if the parameter is a
ParameterExpression (or a QuantumCircuit) by using a Python isinstance()
check, then tries to extract it as a float, and finally stores a
non-parameter object; which is a new variant in the Param enum. This
new variant also lets us simplify the logic around adding gates to the
parameter table as we're able to know ahead of time which gate
parameters are `ParameterExpression`s and which are other objects (and
don't need to be tracked in the parameter table.

Additionally this commit tweaks two tests, the first is
test.python.circuit.library.test_nlocal.TestNLocal.test_parameters_setter
which was adjusted in the previous commit to workaround the bug fixed
by this commit. The second is test.python.circuit.test_parameters which
was testing that a bound ParameterExpression with a value of 0 defaults
to an int which was a side effect of passing an int input to symengine
for the bind value and not part of the api and didn't need to be
checked. This assertion was removed from the test because the rust
representation is only storing f64 values for the numeric parameters
and it is never an int after binding from the Python perspective it
isn't any different to have float(0) and int(0) unless you explicit
isinstance check like the test previously was.

* Fix qasm3 exporter for std gates without stdgates.inc

This commit fixes the handling of standard gates in Qiskit when the user
specifies excluding the use of the stdgates.inc file from the exported
qasm. Previously the object id of the standard gates were used to
maintain a lookup table of the global definitions for all the standard
gates explicitly in the file. However, the rust refactor means that
every time the exporter accesses `circuit.data[x].operation` a new
instance is returned. This means that on subsequent lookups for the
definition the gate definitions are never found. To correct this issue
this commit adds to the lookup table a fallback of the gate name +
parameters to do the lookup for. This should be unique for any standard
gate and not interfere with the previous logic that's still in place and
functional for other custom gate definitions.

While this fixes the logic in the exporter the test is still failing
because the test is asserting the object ids are the same in the qasm3
file, which isn't the case anymore. The test will be updated in a
subsequent commit to validate the qasm3 file is correct without using
a hardcoded object id.

* Fix base scheduler analysis pass duration setting

When ALAPScheduleAnalysis and ASAPScheduleAnalysis were setting the
duration of a gate they were doing `node.op.duration = duration` this
wasn't always working because if `node.op` was a standard gate it
returned a new Python object created from the underlying rust
representation. This commit fixes the passes so that they modify the
duration and then explicit set the operation to update it's rust
representation.

* Fix python lint

* Fix last failing qasm3 test for std gates without stdgates.inc

While the logic for the qasm3 exporter was fixed
in commit a6e69ba4c99cdd2fa50ed10399cada322bc88903 to handle the edge
case of a user specifying that the qasm exporter does not use the
stdgates.inc include file in the output, but also has qiskit's standard
gates in their circuit being exported. The one unit test to provide
coverage for that scenario was not passing because when an id was used
for the gate definitions in the qasm3 file it was being referenced
against a temporary created by accessing a standard gate from the
circuit and the ids weren't the same so the reference string didn't
match what the exporter generated. This commit fixes this by changing
the test to not do an exact string comparison, but instead a line by
line comparison that either does exact equality check or a regex search
for the expected line and the ids are checked as being any 15 character
integer.

* Remove superfluous comment

* Cache imported classes with GILOnceCell

* Remove unused python variables

* Add missing file

* Update QuantumCircuit gate methods to bypass Python object

This commit updates the QuantumCircuit gate methods which add a given
gate to the circuit to bypass the python gate object creation and
directly insert a rust representation of the gate. This avoids a
conversion in the rust side of the code. While in practice this is just
the Python side object creation and a getattr for the rust code to
determine it's a standard gate that we're skipping. This may add up over
time if there are a lot of gates being created by the method.

To accomplish this the rust code handling the mapping of rust
StandardGate variants to the Python classes that represent those gates
needed to be updated as well. By bypassing the python object creation
we need a fallback to populate the gate class for when a user access the
operation object from Python. Previously this mapping was only being
populated at insertion time and if we never insert the python object
(for a circuit created only via the methods) then we need a way to find
what the gate class is. A static lookup table of import paths and class names
are added to `qiskit_circuit::imports` module to faciliate this and
helper functions are added to facilitate interacting with the class
objects that represent each gate.

* Deduplicate gate matrix definitions

* Fix lint

* Attempt to fix qasm3 test failure

* Add compile time option to cache py gate returns for rust std gates

This commit adds a new rust crate feature flag for the qiskit-circuits
and qiskit-pyext that enables caching the output from
CircuitInstruction.operation to python space. Previously, for memory
efficiency we were reconstructing the python object on demand for every
access. This was to avoid carrying around an extra pointer and keeping
the ephemeral python object around longer term if it's only needed once.
But right now nothing is directly using the rust representation yet and
everything is accessing via the python interface, so recreating gate
objects on the fly has a huge performance penalty. To avoid that this
adds caching by default as a temporary solution to avoid this until we
have more usage of the rust representation of gates.

There is an inherent tension between an optimal rust representation
and something that is performant for Python access and there isn't a
clear cut answer on which one is better to optimize for. A build time
feature lets the user pick, if what we settle on for the default doesn't
agree with their priorities or use case. Personally I'd like to see us
disable the caching longer term (hopefully before releasing this
functionality), but that's dependent on a sufficent level of usage from
rust superseding the current Python space usage in the core of Qiskit.

* Add num_nonlocal_gates implementation in rust

This commit adds a native rust implementation to rust for the
num_nonlocal_gates method on QuantumCircuit. Now that we have a rust
representation of gates it is potentially faster to do the count because
the iteration and filtering is done rust side.

* Performance tuning circuit construction

This commit fixes some performance issues with the addition of standard
gates to a circuit. To workaround potential reference cycles in Python
when calling rust we need to check the parameters of the operation. This
was causing our fast path for standard gates to access the `operation`
attribute to get the parameters. This causes the gate to be eagerly
constructed on the getter. However, the reference cycle case can only
happen in situations without a standard gate, and the fast path for
adding standard gates directly won't need to run this so a skip is added
if we're adding a standard gate.

* Add back validation of parameters on gate methods

In the previous commit a side effect of the accidental eager operation
creation was that the parameter input for gates were being validated by
that. By fixing that in the previous commit the validation of input
parameters on the circuit methods was broken. This commit fixes that
oversight and adds back the validation.

* Skip validation on gate creation from rust

* Offload operation copying to rust

This commit fixes a performance regression in the
`QuantumCircuit.copy()` method which was previously using Python to copy
the operations which had extra overhead to go from rust to python and
vice versa. This moves that logic to exist in rust and improve the copy
performance.

* Fix lint

* Perform deepcopy in rust

This commit moves the deepcopy handling to occur solely in Rust.
Previously each instruction would be directly deepcopied by iterating
over the circuit data. However, we can do this rust side now and doing
this is more efficient because while we need to rely on Python to run a
deepcopy we can skip it for the Rust standard gates and rely on Rust to
copy those gates.

* Fix QuantumCircuit.compose() performance regression

This commit fixes a performance regression in the compose() method. This
was caused by the checking for classical conditions in the method
requiring eagerly converting all standard gates to a Python object. This
changes the logic to do this only if we know we have a condition (which
we can determine Python side now).

* Fix map_ops test case with no caching case

* Fix typos in docs

This commit fixes several docs typos that were caught during code review.

Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com>

* Shrink memory usage for extra mutable instruction state

This commit changes how we store the extra mutable instruction state
(condition, duration, unit, and label) for each `CircuitInstruction`
and `PackedInstruction` in the circuit. Previously it was all stored
as separate `Option` fields on the struct, which required at least
a pointer's width for each field which was wasted space the majority of
the time as using these fields are not common. To optimize the memory
layout of the struct this moves these attributes to a new struct which
is put in an `Option>` which reduces it from 4 pointer widths
down to 1 per object. This comes from extra runtime cost from the extra
layer of pointer indirection but as this is the uncommon path this
tradeoff is fine.

* Remove Option<> from params field in CircuitInstruction

This commit removes the Option<> from the params field in
CircuitInstruction. There is no real distinction between an empty vec
and None in this case, so the option just added another layer in the API
that we didn't need to deal with. Also depending on the memory alignment
using an Option might have ended up in a little extra memory usage
too, so removing it removes that potential source of overhead.

* Eagerly construct rust python wrappers in .append()

This commit updates the Python code in QuantumCircuit.append() method
to eagerly construct the rust wrapper objects for python defined circuit
operations.

* Simplify code around handling python errors in rust

* Revert "Skip validation on gate creation from rust"

This reverts commit 2f81bde8bf32b06b4165048896eabdb36470814d. The
validation skipping was unsound in some cases and could lead to invalid
circuit being generated. If we end up needing this as an optimization we
can remove this in the future in a follow-up PR that explores this in
isolation.

* Temporarily use git for qasm3 import

In Qiskit/qiskit-qasm3-import#34 the issue we're hitting caused by
qiskit-qasm3-import using the private circuit attributes removed in this
PR was fixed. This commit temporarily moves to installing it from git so
we can fully run CI. When qiskit-qasm3-import is released we should
revert this commit.

* Fix lint

* Fix lint for real (we really need to use a py312 compatible version of pylint)

* Fix test failure caused by incorrect lint fix

* Relax trait-method typing requirements

* Encapsulate `GILOnceCell` initialisers to local logic

* Simplify Interface for building circuit of standard gates in rust

* Simplify complex64 creation in gate_matrix.rs

This just switches Complex64::new(re, im) to be c64(re, im) to reduce
the amount of typing. c64 needs to be defined inplace so it can be a
const fn.

* Simplify initialization of array of elements that are not Copy (#28)

* Simplify initialization of array of elements that are not Copy

* Only generate array when necessary

* Fix doc typos

Co-authored-by: Kevin Hartman 

* Add conversion trait for OperationType -> OperationInput and simplify CircuitInstruction::replace()

* Use destructuring for operation_type_to_py extra attr handling

* Simplify trait bounds for map_indices()

The map_indices() method previously specified both Iterator and
ExactSizeIterator for it's trait bounds, but Iterator is a supertrait of
ExactSizeIterator and we don't need to explicitly list both. This commit
removes the duplicate trait bound.

* Make Qubit and Clbit newtype member public

As we start to use Qubit and Clbit for creating circuits from accelerate
and other crates in the Qiskit workspace we need to be able to create
instances of them. However, the newtype member BitType was not public
which prevented creating new Qubits. This commit fixes this by making it
public.

* Use snakecase for gate matrix names

* Remove pointless underscore prefix

* Use downcast instead of bound

* Rwork _append reference cycle handling

This commit reworks the multiple borrow handling in the _append() method
to leveraging `Bound.try_borrow()` to return a consistent error message
if we're unable to borrow a CircuitInstruction in the rust code meaning
there is a cyclical reference in the code. Previously we tried to detect
this cycle up-front which added significant overhead for a corner case.

* Make CircuitData.global_phase_param_index a class attr

* Use &[Param] instead of &SmallVec<..> for operation_type_and_data_to_py

* Have get_params_unsorted return a set

* Use lookup table for static property methods of StandardGate

* Use PyTuple::empty_bound()

* Fix lint

* Add missing test method docstring

* Reuse allocations in parameter table update

* Remove unnecessary global phase zeroing

* Move manually set params to a separate function

* Fix release note typo

* Use constant for global-phase index

* Switch requirement to release version

---------

Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com>
Co-authored-by: Jake Lishman 
Co-authored-by: John Lapeyre 
Co-authored-by: Kevin Hartman 
---
 .github/workflows/tests.yml                   |   9 +
 CONTRIBUTING.md                               |  12 +
 Cargo.lock                                    |   4 +
 Cargo.toml                                    |   5 +
 crates/accelerate/Cargo.toml                  |   8 +-
 crates/accelerate/src/isometry.rs             |   2 +-
 crates/accelerate/src/two_qubit_decompose.rs  |  68 +-
 crates/circuit/Cargo.toml                     |  15 +-
 crates/circuit/README.md                      |  63 ++
 crates/circuit/src/bit_data.rs                |  13 +-
 crates/circuit/src/circuit_data.rs            | 911 +++++++++++++++++-
 crates/circuit/src/circuit_instruction.rs     | 698 +++++++++++++-
 crates/circuit/src/dag_node.rs                |  86 +-
 crates/circuit/src/gate_matrix.rs             | 224 +++++
 crates/circuit/src/imports.rs                 | 168 ++++
 crates/circuit/src/interner.rs                |  12 +-
 crates/circuit/src/lib.rs                     |  13 +-
 crates/circuit/src/operations.rs              | 786 +++++++++++++++
 crates/circuit/src/packed_instruction.rs      |  25 -
 crates/circuit/src/parameter_table.rs         | 173 ++++
 crates/pyext/Cargo.toml                       |   1 +
 qiskit/circuit/controlflow/builder.py         |   8 +-
 qiskit/circuit/instruction.py                 |   1 +
 qiskit/circuit/instructionset.py              |   9 +-
 qiskit/circuit/library/blueprintcircuit.py    |   8 +-
 qiskit/circuit/library/standard_gates/ecr.py  |   3 +
 .../library/standard_gates/global_phase.py    |   3 +
 qiskit/circuit/library/standard_gates/h.py    |   3 +
 qiskit/circuit/library/standard_gates/i.py    |   3 +
 qiskit/circuit/library/standard_gates/p.py    |   3 +
 qiskit/circuit/library/standard_gates/rx.py   |   3 +
 qiskit/circuit/library/standard_gates/ry.py   |   3 +
 qiskit/circuit/library/standard_gates/rz.py   |   3 +
 qiskit/circuit/library/standard_gates/swap.py |   3 +
 qiskit/circuit/library/standard_gates/sx.py   |   3 +
 qiskit/circuit/library/standard_gates/u.py    |   3 +
 qiskit/circuit/library/standard_gates/x.py    |   7 +
 qiskit/circuit/library/standard_gates/y.py    |   5 +
 qiskit/circuit/library/standard_gates/z.py    |   5 +
 qiskit/circuit/parametertable.py              | 191 +---
 qiskit/circuit/quantumcircuit.py              | 419 ++++----
 qiskit/circuit/quantumcircuitdata.py          |   4 +-
 qiskit/converters/circuit_to_instruction.py   |  12 +-
 qiskit/qasm3/exporter.py                      |  10 +-
 .../operators/dihedral/dihedral.py            |   3 +-
 .../padding/dynamical_decoupling.py           |  16 +-
 .../scheduling/scheduling/base_scheduler.py   |   5 +-
 .../passes/scheduling/time_unit_conversion.py |   7 +-
 .../circuit-gates-rust-5c6ab6c58f7fd2c9.yaml  |  79 ++
 requirements-optional.txt                     |   2 +-
 setup.py                                      |  12 +
 .../circuit/library/test_blueprintcircuit.py  |   6 +-
 test/python/circuit/test_circuit_data.py      |  25 +-
 .../python/circuit/test_circuit_operations.py |   2 +-
 test/python/circuit/test_compose.py           |   3 +-
 test/python/circuit/test_instructions.py      |  12 +-
 test/python/circuit/test_isometry.py          |   1 -
 test/python/circuit/test_parameters.py        | 191 +---
 test/python/circuit/test_rust_equivalence.py  | 143 +++
 test/python/qasm3/test_export.py              | 201 ++--
 60 files changed, 3780 insertions(+), 936 deletions(-)
 create mode 100644 crates/circuit/src/gate_matrix.rs
 create mode 100644 crates/circuit/src/imports.rs
 create mode 100644 crates/circuit/src/operations.rs
 delete mode 100644 crates/circuit/src/packed_instruction.rs
 create mode 100644 crates/circuit/src/parameter_table.rs
 create mode 100644 releasenotes/notes/circuit-gates-rust-5c6ab6c58f7fd2c9.yaml
 create mode 100644 test/python/circuit/test_rust_equivalence.py

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 08530adfd4f1..20e40dec9824 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -36,6 +36,15 @@ jobs:
           python -m pip install -U -r requirements.txt -c constraints.txt
           python -m pip install -U -r requirements-dev.txt -c constraints.txt
           python -m pip install -c constraints.txt -e .
+        if: matrix.python-version == '3.10'
+        env:
+          QISKIT_NO_CACHE_GATES: 1
+      - name: 'Install dependencies'
+        run: |
+          python -m pip install -U -r requirements.txt -c constraints.txt
+          python -m pip install -U -r requirements-dev.txt -c constraints.txt
+          python -m pip install -c constraints.txt -e .
+        if: matrix.python-version == '3.12'
       - name: 'Install optionals'
         run: |
           python -m pip install -r requirements-optional.txt -c constraints.txt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c75b51750017..4641c7878fc1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -135,6 +135,18 @@ Note that in order to run `python setup.py ...` commands you need have build
 dependency packages installed in your environment, which are listed in the
 `pyproject.toml` file under the `[build-system]` section.
 
+### Compile time options
+
+When building qiskit from source there are options available to control how
+Qiskit is built. Right now the only option is if you set the environment
+variable `QISKIT_NO_CACHE_GATES=1` this will disable runtime caching of
+Python gate objects when accessing them from a `QuantumCircuit` or `DAGCircuit`.
+This makes a tradeoff between runtime performance for Python access and memory
+overhead. Caching gates will result in better runtime for users of Python at
+the cost of increased memory consumption. If you're working with any custom
+transpiler passes written in python or are otherwise using a workflow that
+repeatedly accesses the `operation` attribute of a `CircuitInstruction` or `op`
+attribute of `DAGOpNode` enabling caching is recommended.
 
 ## Issues and pull requests
 
diff --git a/Cargo.lock b/Cargo.lock
index cf8e4f365df9..aefa3c932a04 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1196,7 +1196,11 @@ name = "qiskit-circuit"
 version = "1.2.0"
 dependencies = [
  "hashbrown 0.14.5",
+ "ndarray",
+ "num-complex",
+ "numpy",
  "pyo3",
+ "smallvec",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 2827b2206f4d..13f43cfabcdc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,11 @@ license = "Apache-2.0"
 [workspace.dependencies]
 indexmap.version = "2.2.6"
 hashbrown.version = "0.14.0"
+num-complex = "0.4"
+ndarray = "^0.15.6"
+numpy = "0.21.0"
+smallvec = "1.13"
+
 # Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
 # actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
 # distributions).  We only activate that feature when building the C extension module; we still need
diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml
index 63be9ad90b44..d9865d545437 100644
--- a/crates/accelerate/Cargo.toml
+++ b/crates/accelerate/Cargo.toml
@@ -11,13 +11,13 @@ doctest = false
 
 [dependencies]
 rayon = "1.10"
-numpy = "0.21.0"
+numpy.workspace = true
 rand = "0.8"
 rand_pcg = "0.3"
 rand_distr = "0.4.3"
 ahash = "0.8.11"
 num-traits = "0.2"
-num-complex = "0.4"
+num-complex.workspace = true
 num-bigint = "0.4"
 rustworkx-core = "0.14"
 faer = "0.19.0"
@@ -25,7 +25,7 @@ itertools = "0.13.0"
 qiskit-circuit.workspace = true
 
 [dependencies.smallvec]
-version = "1.13"
+workspace = true
 features = ["union"]
 
 [dependencies.pyo3]
@@ -33,7 +33,7 @@ workspace = true
 features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]
 
 [dependencies.ndarray]
-version = "^0.15.6"
+workspace = true
 features = ["rayon", "approx-0_5"]
 
 [dependencies.approx]
diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs
index a4e83358a7da..a3a8be38dae2 100644
--- a/crates/accelerate/src/isometry.rs
+++ b/crates/accelerate/src/isometry.rs
@@ -23,7 +23,7 @@ use itertools::Itertools;
 use ndarray::prelude::*;
 use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2};
 
-use crate::two_qubit_decompose::ONE_QUBIT_IDENTITY;
+use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY;
 
 /// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or
 /// basis_state=1 respectively
diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs
index 5e833bd86fda..f93eb2a8d99c 100644
--- a/crates/accelerate/src/two_qubit_decompose.rs
+++ b/crates/accelerate/src/two_qubit_decompose.rs
@@ -51,6 +51,7 @@ use rand::prelude::*;
 use rand_distr::StandardNormal;
 use rand_pcg::Pcg64Mcg;
 
+use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
 use qiskit_circuit::SliceOrInt;
 
 const PI2: f64 = PI / 2.0;
@@ -60,11 +61,6 @@ const TWO_PI: f64 = 2.0 * PI;
 
 const C1: c64 = c64 { re: 1.0, im: 0.0 };
 
-pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [
-    [Complex64::new(1., 0.), Complex64::new(0., 0.)],
-    [Complex64::new(0., 0.), Complex64::new(1., 0.)],
-];
-
 static B_NON_NORMALIZED: [[Complex64; 4]; 4] = [
     [
         Complex64::new(1.0, 0.),
@@ -342,54 +338,6 @@ fn rz_matrix(theta: f64) -> Array2 {
     ]
 }
 
-static HGATE: [[Complex64; 2]; 2] = [
-    [
-        Complex64::new(FRAC_1_SQRT_2, 0.),
-        Complex64::new(FRAC_1_SQRT_2, 0.),
-    ],
-    [
-        Complex64::new(FRAC_1_SQRT_2, 0.),
-        Complex64::new(-FRAC_1_SQRT_2, 0.),
-    ],
-];
-
-static CXGATE: [[Complex64; 4]; 4] = [
-    [
-        Complex64::new(1., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-    ],
-    [
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(1., 0.),
-    ],
-    [
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(1., 0.),
-        Complex64::new(0., 0.),
-    ],
-    [
-        Complex64::new(0., 0.),
-        Complex64::new(1., 0.),
-        Complex64::new(0., 0.),
-        Complex64::new(0., 0.),
-    ],
-];
-
-static SXGATE: [[Complex64; 2]; 2] = [
-    [Complex64::new(0.5, 0.5), Complex64::new(0.5, -0.5)],
-    [Complex64::new(0.5, -0.5), Complex64::new(0.5, 0.5)],
-];
-
-static XGATE: [[Complex64; 2]; 2] = [
-    [Complex64::new(0., 0.), Complex64::new(1., 0.)],
-    [Complex64::new(1., 0.), Complex64::new(0., 0.)],
-];
-
 fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2 {
     let identity = aview2(&ONE_QUBIT_IDENTITY);
     let phase = Complex64::new(0., global_phase).exp();
@@ -402,10 +350,10 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2<
             // sequence. If we get a different gate this is getting called
             // by something else and is invalid.
             let gate_matrix = match inst.0.as_ref() {
-                "sx" => aview2(&SXGATE).to_owned(),
+                "sx" => aview2(&SX_GATE).to_owned(),
                 "rz" => rz_matrix(inst.1[0]),
-                "cx" => aview2(&CXGATE).to_owned(),
-                "x" => aview2(&XGATE).to_owned(),
+                "cx" => aview2(&CX_GATE).to_owned(),
+                "x" => aview2(&X_GATE).to_owned(),
                 _ => unreachable!("Undefined gate"),
             };
             (gate_matrix, &inst.2)
@@ -1481,7 +1429,7 @@ impl TwoQubitBasisDecomposer {
         } else {
             euler_matrix_q0 = rz_matrix(euler_q0[0][2] + euler_q0[1][0]).dot(&euler_matrix_q0);
         }
-        euler_matrix_q0 = aview2(&HGATE).dot(&euler_matrix_q0);
+        euler_matrix_q0 = aview2(&H_GATE).dot(&euler_matrix_q0);
         self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q0.view(), 0);
 
         let rx_0 = rx_matrix(euler_q1[0][0]);
@@ -1489,7 +1437,7 @@ impl TwoQubitBasisDecomposer {
         let rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]);
         let mut euler_matrix_q1 = rz.dot(&rx_0);
         euler_matrix_q1 = rx_1.dot(&euler_matrix_q1);
-        euler_matrix_q1 = aview2(&HGATE).dot(&euler_matrix_q1);
+        euler_matrix_q1 = aview2(&H_GATE).dot(&euler_matrix_q1);
         self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q1.view(), 1);
 
         gates.push(("cx".to_string(), smallvec![], smallvec![1, 0]));
@@ -1550,12 +1498,12 @@ impl TwoQubitBasisDecomposer {
             return None;
         }
         gates.push(("cx".to_string(), smallvec![], smallvec![1, 0]));
-        let mut euler_matrix = rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&HGATE));
+        let mut euler_matrix = rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&H_GATE));
         euler_matrix = rx_matrix(euler_q0[3][1]).dot(&euler_matrix);
         euler_matrix = rz_matrix(euler_q0[3][2]).dot(&euler_matrix);
         self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix.view(), 0);
 
-        let mut euler_matrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&HGATE));
+        let mut euler_matrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&H_GATE));
         euler_matrix = rz_matrix(euler_q1[3][1]).dot(&euler_matrix);
         euler_matrix = rx_matrix(euler_q1[3][2]).dot(&euler_matrix);
         self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix.view(), 1);
diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml
index 6ec38392cc38..dd7e878537d9 100644
--- a/crates/circuit/Cargo.toml
+++ b/crates/circuit/Cargo.toml
@@ -11,4 +11,17 @@ doctest = false
 
 [dependencies]
 hashbrown.workspace = true
-pyo3.workspace = true
+num-complex.workspace = true
+ndarray.workspace = true
+numpy.workspace = true
+
+[dependencies.pyo3]
+workspace = true
+features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]
+
+[dependencies.smallvec]
+workspace = true
+features = ["union"]
+
+[features]
+cache_pygates = []
diff --git a/crates/circuit/README.md b/crates/circuit/README.md
index b9375c9f99da..bbb4e54651ad 100644
--- a/crates/circuit/README.md
+++ b/crates/circuit/README.md
@@ -4,3 +4,66 @@ The Rust-based data structures for circuits.
 This currently defines the core data collections for `QuantumCircuit`, but may expand in the future to back `DAGCircuit` as well.
 
 This crate is a very low part of the Rust stack, if not the very lowest.
+
+The data model exposed by this crate is as follows.
+
+## CircuitData
+
+The core representation of a quantum circuit in Rust is the `CircuitData` struct. This containts the list
+of instructions that are comprising the circuit. Each element in this list is modeled by a
+`CircuitInstruction` struct. The `CircuitInstruction` contains the operation object and it's operands.
+This includes the parameters and bits. It also contains the potential mutable state of the Operation representation from the legacy Python data model; namely `duration`, `unit`, `condition`, and `label`.
+In the future we'll be able to remove all of that except for label.
+
+At rest a `CircuitInstruction` is compacted into a `PackedInstruction` which caches reused qargs
+in the instructions to reduce the memory overhead of `CircuitData`. The `PackedInstruction` objects
+get unpacked back to `CircuitInstruction` when accessed for a more convienent working form.
+
+Additionally the `CircuitData` contains a `param_table` field which is used to track parameterized
+instructions that are using python defined `ParameterExpression` objects for any parameters and also
+a global phase field which is used to track the global phase of the circuit.
+
+## Operation Model
+
+In the circuit crate all the operations used in a `CircuitInstruction` are part of the `OperationType`
+enum. The `OperationType` enum has four variants which are used to define the different types of
+operation objects that can be on a circuit:
+
+ - `StandardGate`: a rust native representation of a member of the Qiskit standard gate library. This is
+    an `enum` that enumerates all the gates in the library and statically defines all the gate properties
+    except for gates that take parameters,
+ - `PyGate`: A struct that wraps a gate outside the standard library defined in Python. This struct wraps
+    a `Gate` instance (or subclass) as a `PyObject`. The static properties of this object (such as name,
+    number of qubits, etc) are stored in Rust for performance but the dynamic properties such as
+    the matrix or definition are accessed by calling back into Python to get them from the stored
+    `PyObject`
+ - `PyInstruction`: A struct that wraps an instruction defined in Python. This struct wraps an
+    `Instruction` instance (or subclass) as a `PyObject`. The static properties of this object (such as
+    name, number of qubits, etc) are stored in Rust for performance but the dynamic properties such as
+    the definition are accessed by calling back into Python to get them from the stored `PyObject`. As
+    the primary difference between `Gate` and `Instruction` in the python data model are that `Gate` is a
+    specialized `Instruction` subclass that represents unitary operations the primary difference between
+    this and `PyGate` are that `PyInstruction` will always return `None` when it's matrix is accessed.
+ - `PyOperation`: A struct that wraps an operation defined in Python. This struct wraps an `Operation`
+    instance (or subclass) as a `PyObject`. The static properties of this object (such as name, number
+    of qubits, etc) are stored in Rust for performance. As `Operation` is the base abstract interface
+    definition of what can be put on a circuit this is mostly just a container for custom Python objects.
+    Anything that's operating on a bare operation will likely need to access it via the `PyObject`
+    manually because the interface doesn't define many standard properties outside of what's cached in
+    the struct.
+
+There is also an `Operation` trait defined which defines the common access pattern interface to these
+4 types along with the `OperationType` parent. This trait defines methods to access the standard data
+model attributes of operations in Qiskit. This includes things like the name, number of qubits, the matrix, the definition, etc.
+
+## ParamTable
+
+The `ParamTable` struct is used to track which circuit instructions are using `ParameterExpression`
+objects for any of their parameters. The Python space `ParameterExpression` is comprised of a symengine
+symbolic expression that defines operations using `Parameter` objects. Each `Parameter` is modeled by
+a uuid and a name to uniquely identify it. The parameter table maps the `Parameter` objects to the
+`CircuitInstruction` in the `CircuitData` that are using them. The `Parameter` comprised of 3 `HashMaps` internally that map the uuid (as `u128`, which is accesible in Python by using `uuid.int`) to the `ParamEntry`, the `name` to the uuid, and the uuid to the PyObject for the actual `Parameter`.
+
+The `ParamEntry` is just a `HashSet` of 2-tuples with usize elements. The two usizes represent the instruction index in the `CircuitData` and the index of the `CircuitInstruction.params` field of
+a give instruction where the given `Parameter` is used in the circuit. If the instruction index is
+`GLOBAL_PHASE_MAX`, that points to the global phase property of the circuit instead of a `CircuitInstruction`.
diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs
index 7964ec186e0f..40540f9df5a4 100644
--- a/crates/circuit/src/bit_data.rs
+++ b/crates/circuit/src/bit_data.rs
@@ -12,7 +12,7 @@
 
 use crate::BitType;
 use hashbrown::HashMap;
-use pyo3::exceptions::{PyRuntimeError, PyValueError};
+use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError};
 use pyo3::prelude::*;
 use pyo3::types::PyList;
 use std::fmt::Debug;
@@ -83,6 +83,15 @@ pub(crate) struct BitData {
 
 pub(crate) struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>);
 
+impl<'py> From> for PyErr {
+    fn from(error: BitNotFoundError) -> Self {
+        PyKeyError::new_err(format!(
+            "Bit {:?} has not been added to this circuit.",
+            error.0
+        ))
+    }
+}
+
 impl BitData
 where
     T: From + Copy,
@@ -142,7 +151,7 @@ where
     /// Map the provided native indices to the corresponding Python
     /// bit instances.
     /// Panics if any of the indices are out of range.
-    pub fn map_indices(&self, bits: &[T]) -> impl Iterator> + ExactSizeIterator {
+    pub fn map_indices(&self, bits: &[T]) -> impl ExactSizeIterator> {
         let v: Vec<_> = bits.iter().map(|i| self.get(*i).unwrap()).collect();
         v.into_iter()
     }
diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs
index fbb7c06fc89e..da35787e3207 100644
--- a/crates/circuit/src/circuit_data.rs
+++ b/crates/circuit/src/circuit_data.rs
@@ -10,17 +10,24 @@
 // copyright notice, and modified files need to carry a notice indicating
 // that they have been altered from the originals.
 
-use crate::bit_data::{BitData, BitNotFoundError};
-use crate::circuit_instruction::CircuitInstruction;
-use crate::interner::{CacheFullError, IndexedInterner, Interner, InternerKey};
-use crate::packed_instruction::PackedInstruction;
+use crate::bit_data::BitData;
+use crate::circuit_instruction::{
+    convert_py_to_operation_type, operation_type_and_data_to_py, CircuitInstruction,
+    ExtraInstructionAttributes, OperationInput, PackedInstruction,
+};
+use crate::imports::{BUILTIN_LIST, QUBIT};
+use crate::interner::{IndexedInterner, Interner, InternerKey};
+use crate::operations::{Operation, OperationType, Param, StandardGate};
+use crate::parameter_table::{ParamEntry, ParamTable, GLOBAL_PHASE_INDEX};
 use crate::{Clbit, Qubit, SliceOrInt};
 
-use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError};
+use pyo3::exceptions::{PyIndexError, PyValueError};
 use pyo3::prelude::*;
 use pyo3::types::{PyList, PySet, PySlice, PyTuple, PyType};
-use pyo3::{PyObject, PyResult, PyTraverseError, PyVisit};
-use std::mem;
+use pyo3::{intern, PyTraverseError, PyVisit};
+
+use hashbrown::{HashMap, HashSet};
+use smallvec::SmallVec;
 
 /// A container for :class:`.QuantumCircuit` instruction listings that stores
 /// :class:`.CircuitInstruction` instances in a packed form by interning
@@ -85,33 +92,250 @@ pub struct CircuitData {
     qubits: BitData,
     /// Clbits registered in the circuit.
     clbits: BitData,
+    param_table: ParamTable,
+    #[pyo3(get)]
+    global_phase: Param,
 }
 
-impl<'py> From> for PyErr {
-    fn from(error: BitNotFoundError) -> Self {
-        PyKeyError::new_err(format!(
-            "Bit {:?} has not been added to this circuit.",
-            error.0
-        ))
+impl CircuitData {
+    /// An alternate constructor to build a new `CircuitData` from an iterator
+    /// of standard gates. This can be used to build a circuit from a sequence
+    /// of standard gates, such as for a `StandardGate` definition or circuit
+    /// synthesis without needing to involve Python.
+    ///
+    /// This can be connected with the Python space
+    /// QuantumCircuit.from_circuit_data() constructor to build a full
+    /// QuantumCircuit from Rust.
+    ///
+    /// # Arguments
+    ///
+    /// * py: A GIL handle this is needed to instantiate Qubits in Python space
+    /// * num_qubits: The number of qubits in the circuit. These will be created
+    ///     in Python as loose bits without a register.
+    /// * instructions: An iterator of the standard gate params and qubits to
+    ///     add to the circuit
+    /// * global_phase: The global phase to use for the circuit
+    pub fn from_standard_gates(
+        py: Python,
+        num_qubits: u32,
+        instructions: I,
+        global_phase: Param,
+    ) -> PyResult
+    where
+        I: IntoIterator, SmallVec<[Qubit; 2]>)>,
+    {
+        let instruction_iter = instructions.into_iter();
+        let mut res = CircuitData {
+            data: Vec::with_capacity(instruction_iter.size_hint().0),
+            qargs_interner: IndexedInterner::new(),
+            cargs_interner: IndexedInterner::new(),
+            qubits: BitData::new(py, "qubits".to_string()),
+            clbits: BitData::new(py, "clbits".to_string()),
+            param_table: ParamTable::new(),
+            global_phase,
+        };
+        if num_qubits > 0 {
+            let qubit_cls = QUBIT.get_bound(py);
+            for _i in 0..num_qubits {
+                let bit = qubit_cls.call0()?;
+                res.add_qubit(py, &bit, true)?;
+            }
+        }
+        for (operation, params, qargs) in instruction_iter {
+            let qubits = PyTuple::new_bound(py, res.qubits.map_indices(&qargs)).unbind();
+            let clbits = PyTuple::empty_bound(py).unbind();
+            let inst = res.pack_owned(
+                py,
+                &CircuitInstruction {
+                    operation: OperationType::Standard(operation),
+                    qubits,
+                    clbits,
+                    params,
+                    extra_attrs: None,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: None,
+                },
+            )?;
+            res.data.push(inst);
+        }
+        Ok(res)
     }
-}
 
-impl From for PyErr {
-    fn from(_: CacheFullError) -> Self {
-        PyRuntimeError::new_err("The bit operands cache is full!")
+    fn handle_manual_params(
+        &mut self,
+        py: Python,
+        inst_index: usize,
+        params: &[(usize, Vec)],
+    ) -> PyResult {
+        let mut new_param = false;
+        let mut atomic_parameters: HashMap = HashMap::new();
+        for (param_index, raw_param_objs) in params {
+            raw_param_objs.iter().for_each(|x| {
+                atomic_parameters.insert(
+                    x.getattr(py, intern!(py, "_uuid"))
+                        .expect("Not a parameter")
+                        .getattr(py, intern!(py, "int"))
+                        .expect("Not a uuid")
+                        .extract::(py)
+                        .unwrap(),
+                    x.clone_ref(py),
+                );
+            });
+            for (param_uuid, param_obj) in atomic_parameters.iter() {
+                match self.param_table.table.get_mut(param_uuid) {
+                    Some(entry) => entry.add(inst_index, *param_index),
+                    None => {
+                        new_param = true;
+                        let new_entry = ParamEntry::new(inst_index, *param_index);
+                        self.param_table
+                            .insert(py, param_obj.clone_ref(py), new_entry)?;
+                    }
+                };
+            }
+            atomic_parameters.clear()
+        }
+        Ok(new_param)
+    }
+
+    /// Add an instruction's entries to the parameter table
+    fn update_param_table(
+        &mut self,
+        py: Python,
+        inst_index: usize,
+        params: Option)>>,
+    ) -> PyResult {
+        if let Some(params) = params {
+            return self.handle_manual_params(py, inst_index, ¶ms);
+        }
+        // Update the parameter table
+        let mut new_param = false;
+        let inst_params = &self.data[inst_index].params;
+        if !inst_params.is_empty() {
+            let params: Vec<(usize, PyObject)> = inst_params
+                .iter()
+                .enumerate()
+                .filter_map(|(idx, x)| match x {
+                    Param::ParameterExpression(param_obj) => Some((idx, param_obj.clone_ref(py))),
+                    _ => None,
+                })
+                .collect();
+            if !params.is_empty() {
+                let list_builtin = BUILTIN_LIST.get_bound(py);
+                let mut atomic_parameters: HashMap = HashMap::new();
+                for (param_index, param) in ¶ms {
+                    let temp: PyObject = param.getattr(py, intern!(py, "parameters"))?;
+                    let raw_param_objs: Vec = list_builtin.call1((temp,))?.extract()?;
+                    raw_param_objs.iter().for_each(|x| {
+                        atomic_parameters.insert(
+                            x.getattr(py, intern!(py, "_uuid"))
+                                .expect("Not a parameter")
+                                .getattr(py, intern!(py, "int"))
+                                .expect("Not a uuid")
+                                .extract(py)
+                                .unwrap(),
+                            x.clone_ref(py),
+                        );
+                    });
+                    for (param_uuid, param_obj) in &atomic_parameters {
+                        match self.param_table.table.get_mut(param_uuid) {
+                            Some(entry) => entry.add(inst_index, *param_index),
+                            None => {
+                                new_param = true;
+                                let new_entry = ParamEntry::new(inst_index, *param_index);
+                                self.param_table
+                                    .insert(py, param_obj.clone_ref(py), new_entry)?;
+                            }
+                        };
+                    }
+                    atomic_parameters.clear();
+                }
+            }
+        }
+        Ok(new_param)
+    }
+
+    /// Remove an index's entries from the parameter table.
+    fn remove_from_parameter_table(&mut self, py: Python, inst_index: usize) -> PyResult<()> {
+        let list_builtin = BUILTIN_LIST.get_bound(py);
+        if inst_index == GLOBAL_PHASE_INDEX {
+            if let Param::ParameterExpression(global_phase) = &self.global_phase {
+                let temp: PyObject = global_phase.getattr(py, intern!(py, "parameters"))?;
+                let raw_param_objs: Vec = list_builtin.call1((temp,))?.extract()?;
+                for (param_index, param_obj) in raw_param_objs.iter().enumerate() {
+                    let uuid: u128 = param_obj
+                        .getattr(py, intern!(py, "_uuid"))?
+                        .getattr(py, intern!(py, "int"))?
+                        .extract(py)?;
+                    let name: String = param_obj.getattr(py, intern!(py, "name"))?.extract(py)?;
+                    self.param_table
+                        .discard_references(uuid, inst_index, param_index, name);
+                }
+            }
+        } else if !self.data[inst_index].params.is_empty() {
+            let params: Vec<(usize, PyObject)> = self.data[inst_index]
+                .params
+                .iter()
+                .enumerate()
+                .filter_map(|(idx, x)| match x {
+                    Param::ParameterExpression(param_obj) => Some((idx, param_obj.clone_ref(py))),
+                    _ => None,
+                })
+                .collect();
+            if !params.is_empty() {
+                for (param_index, param) in ¶ms {
+                    let temp: PyObject = param.getattr(py, intern!(py, "parameters"))?;
+                    let raw_param_objs: Vec = list_builtin.call1((temp,))?.extract()?;
+                    let mut atomic_parameters: HashSet<(u128, String)> =
+                        HashSet::with_capacity(params.len());
+                    for x in raw_param_objs {
+                        let uuid = x
+                            .getattr(py, intern!(py, "_uuid"))?
+                            .getattr(py, intern!(py, "int"))?
+                            .extract(py)?;
+                        let name = x.getattr(py, intern!(py, "name"))?.extract(py)?;
+                        atomic_parameters.insert((uuid, name));
+                    }
+                    for (uuid, name) in atomic_parameters {
+                        self.param_table
+                            .discard_references(uuid, inst_index, *param_index, name);
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn reindex_parameter_table(&mut self, py: Python) -> PyResult<()> {
+        self.param_table.clear();
+
+        for inst_index in 0..self.data.len() {
+            self.update_param_table(py, inst_index, None)?;
+        }
+        // Technically we could keep the global phase entry directly if it exists, but we're
+        // the incremental cost is minimal after reindexing everything.
+        self.global_phase(py, self.global_phase.clone())?;
+        Ok(())
+    }
+
+    pub fn append_inner(&mut self, py: Python, value: PyRef) -> PyResult {
+        let packed = self.pack(py, value)?;
+        let new_index = self.data.len();
+        self.data.push(packed);
+        self.update_param_table(py, new_index, None)
     }
 }
 
 #[pymethods]
 impl CircuitData {
     #[new]
-    #[pyo3(signature = (qubits=None, clbits=None, data=None, reserve=0))]
+    #[pyo3(signature = (qubits=None, clbits=None, data=None, reserve=0, global_phase=Param::Float(0.0)))]
     pub fn new(
         py: Python<'_>,
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
         data: Option<&Bound>,
         reserve: usize,
+        global_phase: Param,
     ) -> PyResult {
         let mut self_ = CircuitData {
             data: Vec::new(),
@@ -119,7 +343,10 @@ impl CircuitData {
             cargs_interner: IndexedInterner::new(),
             qubits: BitData::new(py, "qubits".to_string()),
             clbits: BitData::new(py, "clbits".to_string()),
+            param_table: ParamTable::new(),
+            global_phase: Param::Float(0.),
         };
+        self_.global_phase(py, global_phase)?;
         if let Some(qubits) = qubits {
             for bit in qubits.iter()? {
                 self_.add_qubit(py, &bit?, true)?;
@@ -241,17 +468,89 @@ impl CircuitData {
     ///
     /// Returns:
     ///     CircuitData: The shallow copy.
-    pub fn copy(&self, py: Python<'_>) -> PyResult {
+    #[pyo3(signature = (copy_instructions=true, deepcopy=false))]
+    pub fn copy(&self, py: Python<'_>, copy_instructions: bool, deepcopy: bool) -> PyResult {
         let mut res = CircuitData::new(
             py,
             Some(self.qubits.cached().bind(py)),
             Some(self.clbits.cached().bind(py)),
             None,
             0,
+            self.global_phase.clone(),
         )?;
         res.qargs_interner = self.qargs_interner.clone();
         res.cargs_interner = self.cargs_interner.clone();
         res.data.clone_from(&self.data);
+        res.param_table.clone_from(&self.param_table);
+
+        if deepcopy {
+            let deepcopy = py
+                .import_bound(intern!(py, "copy"))?
+                .getattr(intern!(py, "deepcopy"))?;
+            for inst in &mut res.data {
+                match &mut inst.op {
+                    OperationType::Standard(_) => {
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Gate(ref mut op) => {
+                        op.gate = deepcopy.call1((&op.gate,))?.unbind();
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Instruction(ref mut op) => {
+                        op.instruction = deepcopy.call1((&op.instruction,))?.unbind();
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Operation(ref mut op) => {
+                        op.operation = deepcopy.call1((&op.operation,))?.unbind();
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                };
+            }
+        } else if copy_instructions {
+            for inst in &mut res.data {
+                match &mut inst.op {
+                    OperationType::Standard(_) => {
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Gate(ref mut op) => {
+                        op.gate = op.gate.call_method0(py, intern!(py, "copy"))?;
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Instruction(ref mut op) => {
+                        op.instruction = op.instruction.call_method0(py, intern!(py, "copy"))?;
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                    OperationType::Operation(ref mut op) => {
+                        op.operation = op.operation.call_method0(py, intern!(py, "copy"))?;
+                        #[cfg(feature = "cache_pygates")]
+                        {
+                            inst.py_op = None;
+                        }
+                    }
+                };
+            }
+        }
         Ok(res)
     }
 
@@ -290,10 +589,87 @@ impl CircuitData {
     /// Args:
     ///     func (Callable[[:class:`~.Operation`], None]):
     ///         The callable to invoke.
+    #[cfg(not(feature = "cache_pygates"))]
     #[pyo3(signature = (func))]
     pub fn foreach_op(&self, py: Python<'_>, func: &Bound) -> PyResult<()> {
         for inst in self.data.iter() {
-            func.call1((inst.op.bind(py),))?;
+            let label;
+            let duration;
+            let unit;
+            let condition;
+            match &inst.extra_attrs {
+                Some(extra_attrs) => {
+                    label = &extra_attrs.label;
+                    duration = &extra_attrs.duration;
+                    unit = &extra_attrs.unit;
+                    condition = &extra_attrs.condition;
+                }
+                None => {
+                    label = &None;
+                    duration = &None;
+                    unit = &None;
+                    condition = &None;
+                }
+            }
+
+            let op = operation_type_and_data_to_py(
+                py,
+                &inst.op,
+                &inst.params,
+                label,
+                duration,
+                unit,
+                condition,
+            )?;
+            func.call1((op,))?;
+        }
+        Ok(())
+    }
+
+    /// Invokes callable ``func`` with each instruction's operation.
+    ///
+    /// Args:
+    ///     func (Callable[[:class:`~.Operation`], None]):
+    ///         The callable to invoke.
+    #[cfg(feature = "cache_pygates")]
+    #[pyo3(signature = (func))]
+    pub fn foreach_op(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> {
+        for inst in self.data.iter_mut() {
+            let op = match &inst.py_op {
+                Some(op) => op.clone_ref(py),
+                None => {
+                    let label;
+                    let duration;
+                    let unit;
+                    let condition;
+                    match &inst.extra_attrs {
+                        Some(extra_attrs) => {
+                            label = &extra_attrs.label;
+                            duration = &extra_attrs.duration;
+                            unit = &extra_attrs.unit;
+                            condition = &extra_attrs.condition;
+                        }
+                        None => {
+                            label = &None;
+                            duration = &None;
+                            unit = &None;
+                            condition = &None;
+                        }
+                    }
+                    let new_op = operation_type_and_data_to_py(
+                        py,
+                        &inst.op,
+                        &inst.params,
+                        label,
+                        duration,
+                        unit,
+                        condition,
+                    )?;
+                    inst.py_op = Some(new_op.clone_ref(py));
+                    new_op
+                }
+            };
+            func.call1((op,))?;
         }
         Ok(())
     }
@@ -304,10 +680,88 @@ impl CircuitData {
     /// Args:
     ///     func (Callable[[int, :class:`~.Operation`], None]):
     ///         The callable to invoke.
+    #[cfg(not(feature = "cache_pygates"))]
     #[pyo3(signature = (func))]
     pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound) -> PyResult<()> {
         for (index, inst) in self.data.iter().enumerate() {
-            func.call1((index, inst.op.bind(py)))?;
+            let label;
+            let duration;
+            let unit;
+            let condition;
+            match &inst.extra_attrs {
+                Some(extra_attrs) => {
+                    label = &extra_attrs.label;
+                    duration = &extra_attrs.duration;
+                    unit = &extra_attrs.unit;
+                    condition = &extra_attrs.condition;
+                }
+                None => {
+                    label = &None;
+                    duration = &None;
+                    unit = &None;
+                    condition = &None;
+                }
+            }
+
+            let op = operation_type_and_data_to_py(
+                py,
+                &inst.op,
+                &inst.params,
+                label,
+                duration,
+                unit,
+                condition,
+            )?;
+            func.call1((index, op))?;
+        }
+        Ok(())
+    }
+
+    /// Invokes callable ``func`` with the positional index and operation
+    /// of each instruction.
+    ///
+    /// Args:
+    ///     func (Callable[[int, :class:`~.Operation`], None]):
+    ///         The callable to invoke.
+    #[cfg(feature = "cache_pygates")]
+    #[pyo3(signature = (func))]
+    pub fn foreach_op_indexed(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> {
+        for (index, inst) in self.data.iter_mut().enumerate() {
+            let op = match &inst.py_op {
+                Some(op) => op.clone_ref(py),
+                None => {
+                    let label;
+                    let duration;
+                    let unit;
+                    let condition;
+                    match &inst.extra_attrs {
+                        Some(extra_attrs) => {
+                            label = &extra_attrs.label;
+                            duration = &extra_attrs.duration;
+                            unit = &extra_attrs.unit;
+                            condition = &extra_attrs.condition;
+                        }
+                        None => {
+                            label = &None;
+                            duration = &None;
+                            unit = &None;
+                            condition = &None;
+                        }
+                    }
+                    let new_op = operation_type_and_data_to_py(
+                        py,
+                        &inst.op,
+                        &inst.params,
+                        label,
+                        duration,
+                        unit,
+                        condition,
+                    )?;
+                    inst.py_op = Some(new_op.clone_ref(py));
+                    new_op
+                }
+            };
+            func.call1((index, op))?;
         }
         Ok(())
     }
@@ -315,14 +769,187 @@ impl CircuitData {
     /// Invokes callable ``func`` with each instruction's operation,
     /// replacing the operation with the result.
     ///
+    /// .. note::
+    ///
+    ///     This is only to be used by map_vars() in quantumcircuit.py it
+    ///     assumes that a full Python instruction will only be returned from
+    ///     standard gates iff a condition is set.
+    ///
     /// Args:
     ///     func (Callable[[:class:`~.Operation`], :class:`~.Operation`]):
     ///         A callable used to map original operation to their
     ///         replacements.
+    #[cfg(not(feature = "cache_pygates"))]
     #[pyo3(signature = (func))]
     pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> {
         for inst in self.data.iter_mut() {
-            inst.op = func.call1((inst.op.bind(py),))?.into_py(py);
+            let old_op = match &inst.op {
+                OperationType::Standard(op) => {
+                    let label;
+                    let duration;
+                    let unit;
+                    let condition;
+                    match &inst.extra_attrs {
+                        Some(extra_attrs) => {
+                            label = &extra_attrs.label;
+                            duration = &extra_attrs.duration;
+                            unit = &extra_attrs.unit;
+                            condition = &extra_attrs.condition;
+                        }
+                        None => {
+                            label = &None;
+                            duration = &None;
+                            unit = &None;
+                            condition = &None;
+                        }
+                    }
+                    if condition.is_some() {
+                        operation_type_and_data_to_py(
+                            py,
+                            &inst.op,
+                            &inst.params,
+                            label,
+                            duration,
+                            unit,
+                            condition,
+                        )?
+                    } else {
+                        op.into_py(py)
+                    }
+                }
+                OperationType::Gate(op) => op.gate.clone_ref(py),
+                OperationType::Instruction(op) => op.instruction.clone_ref(py),
+                OperationType::Operation(op) => op.operation.clone_ref(py),
+            };
+            let result: OperationInput = func.call1((old_op,))?.extract()?;
+            match result {
+                OperationInput::Standard(op) => {
+                    inst.op = OperationType::Standard(op);
+                }
+                OperationInput::Gate(op) => {
+                    inst.op = OperationType::Gate(op);
+                }
+                OperationInput::Instruction(op) => {
+                    inst.op = OperationType::Instruction(op);
+                }
+                OperationInput::Operation(op) => {
+                    inst.op = OperationType::Operation(op);
+                }
+                OperationInput::Object(new_op) => {
+                    let new_inst_details = convert_py_to_operation_type(py, new_op)?;
+                    inst.op = new_inst_details.operation;
+                    inst.params = new_inst_details.params;
+                    if new_inst_details.label.is_some()
+                        || new_inst_details.duration.is_some()
+                        || new_inst_details.unit.is_some()
+                        || new_inst_details.condition.is_some()
+                    {
+                        inst.extra_attrs = Some(Box::new(ExtraInstructionAttributes {
+                            label: new_inst_details.label,
+                            duration: new_inst_details.duration,
+                            unit: new_inst_details.unit,
+                            condition: new_inst_details.condition,
+                        }))
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+
+    /// Invokes callable ``func`` with each instruction's operation,
+    /// replacing the operation with the result.
+    ///
+    /// .. note::
+    ///
+    ///     This is only to be used by map_vars() in quantumcircuit.py it
+    ///     assumes that a full Python instruction will only be returned from
+    ///     standard gates iff a condition is set.
+    ///
+    /// Args:
+    ///     func (Callable[[:class:`~.Operation`], :class:`~.Operation`]):
+    ///         A callable used to map original operation to their
+    ///         replacements.
+    #[cfg(feature = "cache_pygates")]
+    #[pyo3(signature = (func))]
+    pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> {
+        for inst in self.data.iter_mut() {
+            let old_op = match &inst.py_op {
+                Some(op) => op.clone_ref(py),
+                None => match &inst.op {
+                    OperationType::Standard(op) => {
+                        let label;
+                        let duration;
+                        let unit;
+                        let condition;
+                        match &inst.extra_attrs {
+                            Some(extra_attrs) => {
+                                label = &extra_attrs.label;
+                                duration = &extra_attrs.duration;
+                                unit = &extra_attrs.unit;
+                                condition = &extra_attrs.condition;
+                            }
+                            None => {
+                                label = &None;
+                                duration = &None;
+                                unit = &None;
+                                condition = &None;
+                            }
+                        }
+                        if condition.is_some() {
+                            let new_op = operation_type_and_data_to_py(
+                                py,
+                                &inst.op,
+                                &inst.params,
+                                label,
+                                duration,
+                                unit,
+                                condition,
+                            )?;
+                            inst.py_op = Some(new_op.clone_ref(py));
+                            new_op
+                        } else {
+                            op.into_py(py)
+                        }
+                    }
+                    OperationType::Gate(op) => op.gate.clone_ref(py),
+                    OperationType::Instruction(op) => op.instruction.clone_ref(py),
+                    OperationType::Operation(op) => op.operation.clone_ref(py),
+                },
+            };
+            let result: OperationInput = func.call1((old_op,))?.extract()?;
+            match result {
+                OperationInput::Standard(op) => {
+                    inst.op = OperationType::Standard(op);
+                }
+                OperationInput::Gate(op) => {
+                    inst.op = OperationType::Gate(op);
+                }
+                OperationInput::Instruction(op) => {
+                    inst.op = OperationType::Instruction(op);
+                }
+                OperationInput::Operation(op) => {
+                    inst.op = OperationType::Operation(op);
+                }
+                OperationInput::Object(new_op) => {
+                    let new_inst_details = convert_py_to_operation_type(py, new_op.clone_ref(py))?;
+                    inst.op = new_inst_details.operation;
+                    inst.params = new_inst_details.params;
+                    if new_inst_details.label.is_some()
+                        || new_inst_details.duration.is_some()
+                        || new_inst_details.unit.is_some()
+                        || new_inst_details.condition.is_some()
+                    {
+                        inst.extra_attrs = Some(Box::new(ExtraInstructionAttributes {
+                            label: new_inst_details.label,
+                            duration: new_inst_details.duration,
+                            unit: new_inst_details.unit,
+                            condition: new_inst_details.condition,
+                        }))
+                    }
+                    inst.py_op = Some(new_op);
+                }
+            }
         }
         Ok(())
     }
@@ -385,7 +1012,7 @@ impl CircuitData {
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
     ) -> PyResult<()> {
-        let mut temp = CircuitData::new(py, qubits, clbits, None, 0)?;
+        let mut temp = CircuitData::new(py, qubits, clbits, None, 0, self.global_phase.clone())?;
         if qubits.is_some() {
             if temp.num_qubits() < self.num_qubits() {
                 return Err(PyValueError::new_err(format!(
@@ -394,7 +1021,7 @@ impl CircuitData {
                     self.num_qubits(),
                 )));
             }
-            mem::swap(&mut temp.qubits, &mut self.qubits);
+            std::mem::swap(&mut temp.qubits, &mut self.qubits);
         }
         if clbits.is_some() {
             if temp.num_clbits() < self.num_clbits() {
@@ -404,7 +1031,7 @@ impl CircuitData {
                     self.num_clbits(),
                 )));
             }
-            mem::swap(&mut temp.clbits, &mut self.clbits);
+            std::mem::swap(&mut temp.clbits, &mut self.clbits);
         }
         Ok(())
     }
@@ -430,9 +1057,11 @@ impl CircuitData {
                     py,
                     CircuitInstruction::new(
                         py,
-                        inst.op.clone_ref(py),
+                        inst.op.clone(),
                         self_.qubits.map_indices(qubits.value),
                         self_.clbits.map_indices(clbits.value),
+                        inst.params.clone(),
+                        inst.extra_attrs.clone(),
                     ),
                 )
             } else {
@@ -455,7 +1084,7 @@ impl CircuitData {
         }
     }
 
-    pub fn __delitem__(&mut self, index: SliceOrInt) -> PyResult<()> {
+    pub fn __delitem__(&mut self, py: Python, index: SliceOrInt) -> PyResult<()> {
         match index {
             SliceOrInt::Slice(slice) => {
                 let slice = {
@@ -468,14 +1097,24 @@ impl CircuitData {
                     s
                 };
                 for i in slice.into_iter() {
-                    self.__delitem__(SliceOrInt::Int(i))?;
+                    self.__delitem__(py, SliceOrInt::Int(i))?;
                 }
+                self.reindex_parameter_table(py)?;
                 Ok(())
             }
             SliceOrInt::Int(index) => {
                 let index = self.convert_py_index(index)?;
                 if self.data.get(index).is_some() {
-                    self.data.remove(index);
+                    if index == self.data.len() {
+                        // For individual removal from param table before
+                        // deletion
+                        self.remove_from_parameter_table(py, index)?;
+                        self.data.remove(index);
+                    } else {
+                        // For delete in the middle delete before reindexing
+                        self.data.remove(index);
+                        self.reindex_parameter_table(py)?;
+                    }
                     Ok(())
                 } else {
                     Err(PyIndexError::new_err(format!(
@@ -487,6 +1126,19 @@ impl CircuitData {
         }
     }
 
+    pub fn setitem_no_param_table_update(
+        &mut self,
+        py: Python<'_>,
+        index: isize,
+        value: &Bound,
+    ) -> PyResult<()> {
+        let index = self.convert_py_index(index)?;
+        let value: PyRef = value.downcast()?.borrow();
+        let mut packed = self.pack(py, value)?;
+        std::mem::swap(&mut packed, &mut self.data[index]);
+        Ok(())
+    }
+
     pub fn __setitem__(
         &mut self,
         py: Python<'_>,
@@ -520,7 +1172,7 @@ impl CircuitData {
                         indices.stop,
                         1isize,
                     );
-                    self.__delitem__(SliceOrInt::Slice(slice))?;
+                    self.__delitem__(py, SliceOrInt::Slice(slice))?;
                 } else {
                     // Insert any extra values.
                     for v in values.iter().skip(slice.len()).rev() {
@@ -535,7 +1187,9 @@ impl CircuitData {
                 let index = self.convert_py_index(index)?;
                 let value: PyRef = value.extract()?;
                 let mut packed = self.pack(py, value)?;
-                mem::swap(&mut packed, &mut self.data[index]);
+                self.remove_from_parameter_table(py, index)?;
+                std::mem::swap(&mut packed, &mut self.data[index]);
+                self.update_param_table(py, index, None)?;
                 Ok(())
             }
         }
@@ -548,8 +1202,14 @@ impl CircuitData {
         value: PyRef,
     ) -> PyResult<()> {
         let index = self.convert_py_index_clamped(index);
+        let old_len = self.data.len();
         let packed = self.pack(py, value)?;
         self.data.insert(index, packed);
+        if index == old_len {
+            self.update_param_table(py, old_len, None)?;
+        } else {
+            self.reindex_parameter_table(py)?;
+        }
         Ok(())
     }
 
@@ -557,14 +1217,21 @@ impl CircuitData {
         let index =
             index.unwrap_or_else(|| std::cmp::max(0, self.data.len() as isize - 1).into_py(py));
         let item = self.__getitem__(py, index.bind(py))?;
-        self.__delitem__(index.bind(py).extract()?)?;
+
+        self.__delitem__(py, index.bind(py).extract()?)?;
         Ok(item)
     }
 
-    pub fn append(&mut self, py: Python<'_>, value: PyRef) -> PyResult<()> {
-        let packed = self.pack(py, value)?;
+    pub fn append(
+        &mut self,
+        py: Python<'_>,
+        value: &Bound,
+        params: Option)>>,
+    ) -> PyResult {
+        let packed = self.pack(py, value.try_borrow()?)?;
+        let new_index = self.data.len();
         self.data.push(packed);
-        Ok(())
+        self.update_param_table(py, new_index, params)
     }
 
     pub fn extend(&mut self, py: Python<'_>, itr: &Bound) -> PyResult<()> {
@@ -597,28 +1264,33 @@ impl CircuitData {
                             .unwrap())
                     })
                     .collect::>>()?;
-
+                let new_index = self.data.len();
                 let qubits_id =
                     Interner::intern(&mut self.qargs_interner, InternerKey::Value(qubits))?;
                 let clbits_id =
                     Interner::intern(&mut self.cargs_interner, InternerKey::Value(clbits))?;
                 self.data.push(PackedInstruction {
-                    op: inst.op.clone_ref(py),
+                    op: inst.op.clone(),
                     qubits_id: qubits_id.index,
                     clbits_id: clbits_id.index,
+                    params: inst.params.clone(),
+                    extra_attrs: inst.extra_attrs.clone(),
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: inst.py_op.clone(),
                 });
+                self.update_param_table(py, new_index, None)?;
             }
             return Ok(());
         }
-
         for v in itr.iter()? {
-            self.append(py, v?.extract()?)?;
+            self.append_inner(py, v?.extract()?)?;
         }
         Ok(())
     }
 
     pub fn clear(&mut self, _py: Python<'_>) -> PyResult<()> {
         std::mem::take(&mut self.data);
+        self.param_table.clear();
         Ok(())
     }
 
@@ -656,9 +1328,6 @@ impl CircuitData {
     }
 
     fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
-        for packed in self.data.iter() {
-            visit.call(&packed.op)?;
-        }
         for bit in self.qubits.bits().iter().chain(self.clbits.bits().iter()) {
             visit.call(bit)?;
         }
@@ -678,6 +1347,128 @@ impl CircuitData {
         self.qubits.dispose();
         self.clbits.dispose();
     }
+
+    #[setter]
+    pub fn global_phase(&mut self, py: Python, angle: Param) -> PyResult<()> {
+        let list_builtin = BUILTIN_LIST.get_bound(py);
+        self.remove_from_parameter_table(py, GLOBAL_PHASE_INDEX)?;
+        match angle {
+            Param::Float(angle) => {
+                self.global_phase = Param::Float(angle.rem_euclid(2. * std::f64::consts::PI));
+            }
+            Param::ParameterExpression(angle) => {
+                let temp: PyObject = angle.getattr(py, intern!(py, "parameters"))?;
+                let raw_param_objs: Vec = list_builtin.call1((temp,))?.extract()?;
+
+                for (param_index, param_obj) in raw_param_objs.into_iter().enumerate() {
+                    let param_uuid: u128 = param_obj
+                        .getattr(py, intern!(py, "_uuid"))?
+                        .getattr(py, intern!(py, "int"))?
+                        .extract(py)?;
+                    match self.param_table.table.get_mut(¶m_uuid) {
+                        Some(entry) => entry.add(GLOBAL_PHASE_INDEX, param_index),
+                        None => {
+                            let new_entry = ParamEntry::new(GLOBAL_PHASE_INDEX, param_index);
+                            self.param_table.insert(py, param_obj, new_entry)?;
+                        }
+                    };
+                }
+                self.global_phase = Param::ParameterExpression(angle);
+            }
+            Param::Obj(_) => return Err(PyValueError::new_err("Invalid type for global phase")),
+        };
+        Ok(())
+    }
+
+    /// Get the global_phase sentinel value
+    #[classattr]
+    pub const fn global_phase_param_index() -> usize {
+        GLOBAL_PHASE_INDEX
+    }
+
+    // Below are functions to interact with the parameter table. These methods
+    // are done to avoid needing to deal with shared references and provide
+    // an entry point via python through an owned CircuitData object.
+    pub fn num_params(&self) -> usize {
+        self.param_table.table.len()
+    }
+
+    pub fn get_param_from_name(&self, py: Python, name: String) -> Option {
+        self.param_table.get_param_from_name(py, name)
+    }
+
+    pub fn get_params_unsorted(&self, py: Python) -> PyResult> {
+        Ok(PySet::new_bound(py, self.param_table.uuid_map.values())?.unbind())
+    }
+
+    pub fn pop_param(
+        &mut self,
+        py: Python,
+        uuid: u128,
+        name: String,
+        default: PyObject,
+    ) -> PyObject {
+        match self.param_table.pop(uuid, name) {
+            Some(res) => res.into_py(py),
+            None => default.clone_ref(py),
+        }
+    }
+
+    pub fn _get_param(&self, py: Python, uuid: u128) -> PyObject {
+        self.param_table.table[&uuid].clone().into_py(py)
+    }
+
+    pub fn contains_param(&self, uuid: u128) -> bool {
+        self.param_table.table.contains_key(&uuid)
+    }
+
+    pub fn add_new_parameter(
+        &mut self,
+        py: Python,
+        param: PyObject,
+        inst_index: usize,
+        param_index: usize,
+    ) -> PyResult<()> {
+        self.param_table.insert(
+            py,
+            param.clone_ref(py),
+            ParamEntry::new(inst_index, param_index),
+        )?;
+        Ok(())
+    }
+
+    pub fn update_parameter_entry(
+        &mut self,
+        uuid: u128,
+        inst_index: usize,
+        param_index: usize,
+    ) -> PyResult<()> {
+        match self.param_table.table.get_mut(&uuid) {
+            Some(entry) => {
+                entry.add(inst_index, param_index);
+                Ok(())
+            }
+            None => Err(PyIndexError::new_err(format!(
+                "Invalid parameter uuid: {:?}",
+                uuid
+            ))),
+        }
+    }
+
+    pub fn _get_entry_count(&self, py: Python, param_obj: PyObject) -> PyResult {
+        let uuid: u128 = param_obj
+            .getattr(py, intern!(py, "_uuid"))?
+            .getattr(py, intern!(py, "int"))?
+            .extract(py)?;
+        Ok(self.param_table.table[&uuid].index_ids.len())
+    }
+
+    pub fn num_nonlocal_gates(&self) -> usize {
+        self.data
+            .iter()
+            .filter(|inst| inst.op.num_qubits() > 1 && !inst.op.directive())
+            .count()
+    }
 }
 
 impl CircuitData {
@@ -730,23 +1521,43 @@ impl CircuitData {
         Ok(index as usize)
     }
 
-    fn pack(
-        &mut self,
-        py: Python,
-        value: PyRef,
-    ) -> PyResult {
+    fn pack(&mut self, py: Python, inst: PyRef) -> PyResult {
+        let qubits = Interner::intern(
+            &mut self.qargs_interner,
+            InternerKey::Value(self.qubits.map_bits(inst.qubits.bind(py))?.collect()),
+        )?;
+        let clbits = Interner::intern(
+            &mut self.cargs_interner,
+            InternerKey::Value(self.clbits.map_bits(inst.clbits.bind(py))?.collect()),
+        )?;
+        Ok(PackedInstruction {
+            op: inst.operation.clone(),
+            qubits_id: qubits.index,
+            clbits_id: clbits.index,
+            params: inst.params.clone(),
+            extra_attrs: inst.extra_attrs.clone(),
+            #[cfg(feature = "cache_pygates")]
+            py_op: inst.py_op.clone(),
+        })
+    }
+
+    fn pack_owned(&mut self, py: Python, inst: &CircuitInstruction) -> PyResult {
         let qubits = Interner::intern(
             &mut self.qargs_interner,
-            InternerKey::Value(self.qubits.map_bits(value.qubits.bind(py))?.collect()),
+            InternerKey::Value(self.qubits.map_bits(inst.qubits.bind(py))?.collect()),
         )?;
         let clbits = Interner::intern(
             &mut self.cargs_interner,
-            InternerKey::Value(self.clbits.map_bits(value.clbits.bind(py))?.collect()),
+            InternerKey::Value(self.clbits.map_bits(inst.clbits.bind(py))?.collect()),
         )?;
         Ok(PackedInstruction {
-            op: value.operation.clone_ref(py),
+            op: inst.operation.clone(),
             qubits_id: qubits.index,
             clbits_id: clbits.index,
+            params: inst.params.clone(),
+            extra_attrs: inst.extra_attrs.clone(),
+            #[cfg(feature = "cache_pygates")]
+            py_op: inst.py_op.clone(),
         })
     }
 }
diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs
index ac61ae81a619..2bb90367082d 100644
--- a/crates/circuit/src/circuit_instruction.rs
+++ b/crates/circuit/src/circuit_instruction.rs
@@ -11,9 +11,45 @@
 // that they have been altered from the originals.
 
 use pyo3::basic::CompareOp;
+use pyo3::exceptions::PyValueError;
 use pyo3::prelude::*;
-use pyo3::types::{PyList, PyTuple};
-use pyo3::{PyObject, PyResult};
+use pyo3::types::{IntoPyDict, PyList, PyTuple, PyType};
+use pyo3::{intern, IntoPy, PyObject, PyResult};
+use smallvec::{smallvec, SmallVec};
+
+use crate::imports::{
+    get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION,
+    SINGLETON_CONTROLLED_GATE, SINGLETON_GATE,
+};
+use crate::interner::Index;
+use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate};
+
+/// These are extra mutable attributes for a circuit instruction's state. In general we don't
+/// typically deal with this in rust space and the majority of the time they're not used in Python
+/// space either. To save memory these are put in a separate struct and are stored inside a
+/// `Box` on `CircuitInstruction` and `PackedInstruction`.
+#[derive(Debug, Clone)]
+pub struct ExtraInstructionAttributes {
+    pub label: Option,
+    pub duration: Option,
+    pub unit: Option,
+    pub condition: Option,
+}
+
+/// Private type used to store instructions with interned arg lists.
+#[derive(Clone, Debug)]
+pub(crate) struct PackedInstruction {
+    /// The Python-side operation instance.
+    pub op: OperationType,
+    /// The index under which the interner has stored `qubits`.
+    pub qubits_id: Index,
+    /// The index under which the interner has stored `clbits`.
+    pub clbits_id: Index,
+    pub params: SmallVec<[Param; 3]>,
+    pub extra_attrs: Option>,
+    #[cfg(feature = "cache_pygates")]
+    pub py_op: Option,
+}
 
 /// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and
 /// various operands.
@@ -47,28 +83,45 @@ use pyo3::{PyObject, PyResult};
 ///     mutations of the object do not invalidate the types, nor the restrictions placed on it by
 ///     its context.  Typically this will mean, for example, that :attr:`qubits` must be a sequence
 ///     of distinct items, with no duplicates.
-#[pyclass(
-    freelist = 20,
-    sequence,
-    get_all,
-    module = "qiskit._accelerate.circuit"
-)]
+#[pyclass(freelist = 20, sequence, module = "qiskit._accelerate.circuit")]
 #[derive(Clone, Debug)]
 pub struct CircuitInstruction {
-    /// The logical operation that this instruction represents an execution of.
-    pub operation: PyObject,
+    pub operation: OperationType,
     /// A sequence of the qubits that the operation is applied to.
+    #[pyo3(get)]
     pub qubits: Py,
     /// A sequence of the classical bits that this operation reads from or writes to.
+    #[pyo3(get)]
     pub clbits: Py,
+    pub params: SmallVec<[Param; 3]>,
+    pub extra_attrs: Option>,
+    #[cfg(feature = "cache_pygates")]
+    pub py_op: Option,
+}
+
+/// This enum is for backwards compatibility if a user was doing something from
+/// Python like CircuitInstruction(SXGate(), [qr[0]], []) by passing a python
+/// gate object directly to a CircuitInstruction. In this case we need to
+/// create a rust side object from the pyobject in CircuitInstruction.new()
+/// With the `Object` variant which will convert the python object to a rust
+/// `OperationType`
+#[derive(FromPyObject, Debug)]
+pub enum OperationInput {
+    Standard(StandardGate),
+    Gate(PyGate),
+    Instruction(PyInstruction),
+    Operation(PyOperation),
+    Object(PyObject),
 }
 
 impl CircuitInstruction {
     pub fn new(
         py: Python,
-        operation: PyObject,
+        operation: OperationType,
         qubits: impl IntoIterator,
         clbits: impl IntoIterator,
+        params: SmallVec<[Param; 3]>,
+        extra_attrs: Option>,
     ) -> Self
     where
         T1: ToPyObject,
@@ -80,19 +133,41 @@ impl CircuitInstruction {
             operation,
             qubits: PyTuple::new_bound(py, qubits).unbind(),
             clbits: PyTuple::new_bound(py, clbits).unbind(),
+            params,
+            extra_attrs,
+            #[cfg(feature = "cache_pygates")]
+            py_op: None,
+        }
+    }
+}
+
+impl From for OperationInput {
+    fn from(value: OperationType) -> Self {
+        match value {
+            OperationType::Standard(op) => Self::Standard(op),
+            OperationType::Gate(gate) => Self::Gate(gate),
+            OperationType::Instruction(inst) => Self::Instruction(inst),
+            OperationType::Operation(op) => Self::Operation(op),
         }
     }
 }
 
 #[pymethods]
 impl CircuitInstruction {
+    #[allow(clippy::too_many_arguments)]
     #[new]
+    #[pyo3(signature = (operation, qubits=None, clbits=None, params=smallvec![], label=None, duration=None, unit=None, condition=None))]
     pub fn py_new(
         py: Python<'_>,
-        operation: PyObject,
+        operation: OperationInput,
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
-    ) -> PyResult> {
+        params: SmallVec<[Param; 3]>,
+        label: Option,
+        duration: Option,
+        unit: Option,
+        condition: Option,
+    ) -> PyResult {
         fn as_tuple(py: Python<'_>, seq: Option<&Bound>) -> PyResult> {
             match seq {
                 None => Ok(PyTuple::empty_bound(py).unbind()),
@@ -116,14 +191,136 @@ impl CircuitInstruction {
             }
         }
 
-        Py::new(
-            py,
-            CircuitInstruction {
-                operation,
-                qubits: as_tuple(py, qubits)?,
-                clbits: as_tuple(py, clbits)?,
-            },
-        )
+        let extra_attrs =
+            if label.is_some() || duration.is_some() || unit.is_some() || condition.is_some() {
+                Some(Box::new(ExtraInstructionAttributes {
+                    label,
+                    duration,
+                    unit,
+                    condition,
+                }))
+            } else {
+                None
+            };
+
+        match operation {
+            OperationInput::Standard(operation) => {
+                let operation = OperationType::Standard(operation);
+                Ok(CircuitInstruction {
+                    operation,
+                    qubits: as_tuple(py, qubits)?,
+                    clbits: as_tuple(py, clbits)?,
+                    params,
+                    extra_attrs,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: None,
+                })
+            }
+            OperationInput::Gate(operation) => {
+                let operation = OperationType::Gate(operation);
+                Ok(CircuitInstruction {
+                    operation,
+                    qubits: as_tuple(py, qubits)?,
+                    clbits: as_tuple(py, clbits)?,
+                    params,
+                    extra_attrs,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: None,
+                })
+            }
+            OperationInput::Instruction(operation) => {
+                let operation = OperationType::Instruction(operation);
+                Ok(CircuitInstruction {
+                    operation,
+                    qubits: as_tuple(py, qubits)?,
+                    clbits: as_tuple(py, clbits)?,
+                    params,
+                    extra_attrs,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: None,
+                })
+            }
+            OperationInput::Operation(operation) => {
+                let operation = OperationType::Operation(operation);
+                Ok(CircuitInstruction {
+                    operation,
+                    qubits: as_tuple(py, qubits)?,
+                    clbits: as_tuple(py, clbits)?,
+                    params,
+                    extra_attrs,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: None,
+                })
+            }
+            OperationInput::Object(old_op) => {
+                let op = convert_py_to_operation_type(py, old_op.clone_ref(py))?;
+                let extra_attrs = if op.label.is_some()
+                    || op.duration.is_some()
+                    || op.unit.is_some()
+                    || op.condition.is_some()
+                {
+                    Some(Box::new(ExtraInstructionAttributes {
+                        label: op.label,
+                        duration: op.duration,
+                        unit: op.unit,
+                        condition: op.condition,
+                    }))
+                } else {
+                    None
+                };
+
+                match op.operation {
+                    OperationType::Standard(operation) => {
+                        let operation = OperationType::Standard(operation);
+                        Ok(CircuitInstruction {
+                            operation,
+                            qubits: as_tuple(py, qubits)?,
+                            clbits: as_tuple(py, clbits)?,
+                            params: op.params,
+                            extra_attrs,
+                            #[cfg(feature = "cache_pygates")]
+                            py_op: Some(old_op.clone_ref(py)),
+                        })
+                    }
+                    OperationType::Gate(operation) => {
+                        let operation = OperationType::Gate(operation);
+                        Ok(CircuitInstruction {
+                            operation,
+                            qubits: as_tuple(py, qubits)?,
+                            clbits: as_tuple(py, clbits)?,
+                            params: op.params,
+                            extra_attrs,
+                            #[cfg(feature = "cache_pygates")]
+                            py_op: Some(old_op.clone_ref(py)),
+                        })
+                    }
+                    OperationType::Instruction(operation) => {
+                        let operation = OperationType::Instruction(operation);
+                        Ok(CircuitInstruction {
+                            operation,
+                            qubits: as_tuple(py, qubits)?,
+                            clbits: as_tuple(py, clbits)?,
+                            params: op.params,
+                            extra_attrs,
+                            #[cfg(feature = "cache_pygates")]
+                            py_op: Some(old_op.clone_ref(py)),
+                        })
+                    }
+                    OperationType::Operation(operation) => {
+                        let operation = OperationType::Operation(operation);
+                        Ok(CircuitInstruction {
+                            operation,
+                            qubits: as_tuple(py, qubits)?,
+                            clbits: as_tuple(py, clbits)?,
+                            params: op.params,
+                            extra_attrs,
+                            #[cfg(feature = "cache_pygates")]
+                            py_op: Some(old_op.clone_ref(py)),
+                        })
+                    }
+                }
+            }
+        }
     }
 
     /// Returns a shallow copy.
@@ -134,28 +331,127 @@ impl CircuitInstruction {
         self.clone()
     }
 
+    /// The logical operation that this instruction represents an execution of.
+    #[cfg(not(feature = "cache_pygates"))]
+    #[getter]
+    pub fn operation(&self, py: Python) -> PyResult {
+        operation_type_to_py(py, self)
+    }
+
+    #[cfg(feature = "cache_pygates")]
+    #[getter]
+    pub fn operation(&mut self, py: Python) -> PyResult {
+        Ok(match &self.py_op {
+            Some(op) => op.clone_ref(py),
+            None => {
+                let op = operation_type_to_py(py, self)?;
+                self.py_op = Some(op.clone_ref(py));
+                op
+            }
+        })
+    }
+
     /// Creates a shallow copy with the given fields replaced.
     ///
     /// Returns:
     ///     CircuitInstruction: A new instance with the given fields replaced.
+    #[allow(clippy::too_many_arguments)]
     pub fn replace(
         &self,
         py: Python<'_>,
-        operation: Option,
+        operation: Option,
         qubits: Option<&Bound>,
         clbits: Option<&Bound>,
-    ) -> PyResult> {
+        params: Option>,
+        label: Option,
+        duration: Option,
+        unit: Option,
+        condition: Option,
+    ) -> PyResult {
+        let operation = operation.unwrap_or_else(|| self.operation.clone().into());
+
+        let params = match params {
+            Some(params) => params,
+            None => self.params.clone(),
+        };
+
+        let label = match label {
+            Some(label) => Some(label),
+            None => match &self.extra_attrs {
+                Some(extra_attrs) => extra_attrs.label.clone(),
+                None => None,
+            },
+        };
+        let duration = match duration {
+            Some(duration) => Some(duration),
+            None => match &self.extra_attrs {
+                Some(extra_attrs) => extra_attrs.duration.clone(),
+                None => None,
+            },
+        };
+
+        let unit: Option = match unit {
+            Some(unit) => Some(unit),
+            None => match &self.extra_attrs {
+                Some(extra_attrs) => extra_attrs.unit.clone(),
+                None => None,
+            },
+        };
+
+        let condition: Option = match condition {
+            Some(condition) => Some(condition),
+            None => match &self.extra_attrs {
+                Some(extra_attrs) => extra_attrs.condition.clone(),
+                None => None,
+            },
+        };
+
         CircuitInstruction::py_new(
             py,
-            operation.unwrap_or_else(|| self.operation.clone_ref(py)),
+            operation,
             Some(qubits.unwrap_or_else(|| self.qubits.bind(py))),
             Some(clbits.unwrap_or_else(|| self.clbits.bind(py))),
+            params,
+            label,
+            duration,
+            unit,
+            condition,
         )
     }
 
-    fn __getnewargs__(&self, py: Python<'_>) -> PyResult {
+    fn __getstate__(&self, py: Python<'_>) -> PyResult {
         Ok((
-            self.operation.bind(py),
+            operation_type_to_py(py, self)?,
+            self.qubits.bind(py),
+            self.clbits.bind(py),
+        )
+            .into_py(py))
+    }
+
+    fn __setstate__(&mut self, py: Python<'_>, state: &Bound) -> PyResult<()> {
+        let op = convert_py_to_operation_type(py, state.get_item(0)?.into())?;
+        self.operation = op.operation;
+        self.params = op.params;
+        self.qubits = state.get_item(1)?.extract()?;
+        self.clbits = state.get_item(2)?.extract()?;
+        if op.label.is_some()
+            || op.duration.is_some()
+            || op.unit.is_some()
+            || op.condition.is_some()
+        {
+            self.extra_attrs = Some(Box::new(ExtraInstructionAttributes {
+                label: op.label,
+                duration: op.duration,
+                unit: op.unit,
+                condition: op.condition,
+            }));
+        }
+        Ok(())
+    }
+
+    pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult {
+        Ok((
+            operation_type_to_py(py, self)?,
             self.qubits.bind(py),
             self.clbits.bind(py),
         )
@@ -172,7 +468,7 @@ impl CircuitInstruction {
             , clbits={}\
             )",
             type_name,
-            r.operation.bind(py).repr()?,
+            operation_type_to_py(py, &r)?,
             r.qubits.bind(py).repr()?,
             r.clbits.bind(py).repr()?
         ))
@@ -184,23 +480,50 @@ impl CircuitInstruction {
     // the interface to behave exactly like the old 3-tuple `(inst, qargs, cargs)` if it's treated
     // like that via unpacking or similar.  That means that the `parameters` field is completely
     // absent, and the qubits and clbits must be converted to lists.
-    pub fn _legacy_format<'py>(&self, py: Python<'py>) -> Bound<'py, PyTuple> {
-        PyTuple::new_bound(
+    #[cfg(not(feature = "cache_pygates"))]
+    pub fn _legacy_format<'py>(&self, py: Python<'py>) -> PyResult> {
+        let op = operation_type_to_py(py, self)?;
+
+        Ok(PyTuple::new_bound(
             py,
-            [
-                self.operation.bind(py),
-                &self.qubits.bind(py).to_list(),
-                &self.clbits.bind(py).to_list(),
-            ],
-        )
+            [op, self.qubits.to_object(py), self.clbits.to_object(py)],
+        ))
     }
 
+    #[cfg(feature = "cache_pygates")]
+    pub fn _legacy_format<'py>(&mut self, py: Python<'py>) -> PyResult> {
+        let op = match &self.py_op {
+            Some(op) => op.clone_ref(py),
+            None => {
+                let op = operation_type_to_py(py, self)?;
+                self.py_op = Some(op.clone_ref(py));
+                op
+            }
+        };
+        Ok(PyTuple::new_bound(
+            py,
+            [op, self.qubits.to_object(py), self.clbits.to_object(py)],
+        ))
+    }
+
+    #[cfg(not(feature = "cache_pygates"))]
     pub fn __getitem__(&self, py: Python<'_>, key: &Bound) -> PyResult {
-        Ok(self._legacy_format(py).as_any().get_item(key)?.into_py(py))
+        Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
     }
 
+    #[cfg(feature = "cache_pygates")]
+    pub fn __getitem__(&mut self, py: Python<'_>, key: &Bound) -> PyResult {
+        Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
+    }
+
+    #[cfg(not(feature = "cache_pygates"))]
     pub fn __iter__(&self, py: Python<'_>) -> PyResult {
-        Ok(self._legacy_format(py).as_any().iter()?.into_py(py))
+        Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
+    }
+
+    #[cfg(feature = "cache_pygates")]
+    pub fn __iter__(&mut self, py: Python<'_>) -> PyResult {
+        Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
     }
 
     pub fn __len__(&self) -> usize {
@@ -227,16 +550,94 @@ impl CircuitInstruction {
                 let other: PyResult> = other.extract();
                 return other.map_or(Ok(Some(false)), |v| {
                     let v = v.try_borrow()?;
+                    let op_eq = match &self_.operation {
+                        OperationType::Standard(op) => {
+                            if let OperationType::Standard(other) = &v.operation {
+                                if op != other {
+                                    false
+                                } else {
+                                    let other_params = &v.params;
+                                    let mut out = true;
+                                    for (param_a, param_b) in self_.params.iter().zip(other_params)
+                                    {
+                                        match param_a {
+                                            Param::Float(val_a) => {
+                                                if let Param::Float(val_b) = param_b {
+                                                    if val_a != val_b {
+                                                        out = false;
+                                                        break;
+                                                    }
+                                                } else {
+                                                    out = false;
+                                                    break;
+                                                }
+                                            }
+                                            Param::ParameterExpression(val_a) => {
+                                                if let Param::ParameterExpression(val_b) = param_b {
+                                                    if !val_a.bind(py).eq(val_b.bind(py))? {
+                                                        out = false;
+                                                        break;
+                                                    }
+                                                } else {
+                                                    out = false;
+                                                    break;
+                                                }
+                                            }
+                                            Param::Obj(val_a) => {
+                                                if let Param::Obj(val_b) = param_b {
+                                                    if !val_a.bind(py).eq(val_b.bind(py))? {
+                                                        out = false;
+                                                        break;
+                                                    }
+                                                } else {
+                                                    out = false;
+                                                    break;
+                                                }
+                                            }
+                                        }
+                                    }
+                                    out
+                                }
+                            } else {
+                                false
+                            }
+                        }
+                        OperationType::Gate(op) => {
+                            if let OperationType::Gate(other) = &v.operation {
+                                op.gate.bind(py).eq(other.gate.bind(py))?
+                            } else {
+                                false
+                            }
+                        }
+                        OperationType::Instruction(op) => {
+                            if let OperationType::Instruction(other) = &v.operation {
+                                op.instruction.bind(py).eq(other.instruction.bind(py))?
+                            } else {
+                                false
+                            }
+                        }
+                        OperationType::Operation(op) => {
+                            if let OperationType::Operation(other) = &v.operation {
+                                op.operation.bind(py).eq(other.operation.bind(py))?
+                            } else {
+                                false
+                            }
+                        }
+                    };
+
                     Ok(Some(
                         self_.clbits.bind(py).eq(v.clbits.bind(py))?
                             && self_.qubits.bind(py).eq(v.qubits.bind(py))?
-                            && self_.operation.bind(py).eq(v.operation.bind(py))?,
+                            && op_eq,
                     ))
                 });
             }
 
             if other.is_instance_of::() {
-                return Ok(Some(self_._legacy_format(py).eq(other)?));
+                #[cfg(feature = "cache_pygates")]
+                let mut self_ = self_.clone();
+                let legacy_format = self_._legacy_format(py)?;
+                return Ok(Some(legacy_format.eq(other)?));
             }
 
             Ok(None)
@@ -255,3 +656,222 @@ impl CircuitInstruction {
         }
     }
 }
+
+/// Take a reference to a `CircuitInstruction` and convert the operation
+/// inside that to a python side object.
+pub(crate) fn operation_type_to_py(
+    py: Python,
+    circuit_inst: &CircuitInstruction,
+) -> PyResult {
+    let (label, duration, unit, condition) = match &circuit_inst.extra_attrs {
+        None => (None, None, None, None),
+        Some(extra_attrs) => (
+            extra_attrs.label.clone(),
+            extra_attrs.duration.clone(),
+            extra_attrs.unit.clone(),
+            extra_attrs.condition.clone(),
+        ),
+    };
+    operation_type_and_data_to_py(
+        py,
+        &circuit_inst.operation,
+        &circuit_inst.params,
+        &label,
+        &duration,
+        &unit,
+        &condition,
+    )
+}
+
+/// Take an OperationType and the other mutable state fields from a
+/// rust instruction representation and return a PyObject representing
+/// a Python side full-fat Qiskit operation as a PyObject. This is typically
+/// used by accessor functions that need to return an operation to Qiskit, such
+/// as accesing `CircuitInstruction.operation`.
+pub(crate) fn operation_type_and_data_to_py(
+    py: Python,
+    operation: &OperationType,
+    params: &[Param],
+    label: &Option,
+    duration: &Option,
+    unit: &Option,
+    condition: &Option,
+) -> PyResult {
+    match &operation {
+        OperationType::Standard(op) => {
+            let gate_class: &PyObject = &get_std_gate_class(py, *op)?;
+
+            let args = if params.is_empty() {
+                PyTuple::empty_bound(py)
+            } else {
+                PyTuple::new_bound(py, params)
+            };
+            let kwargs = [
+                ("label", label.to_object(py)),
+                ("unit", unit.to_object(py)),
+                ("duration", duration.to_object(py)),
+            ]
+            .into_py_dict_bound(py);
+            let mut out = gate_class.call_bound(py, args, Some(&kwargs))?;
+            if condition.is_some() {
+                out = out.call_method0(py, "to_mutable")?;
+                out.setattr(py, "condition", condition.to_object(py))?;
+            }
+            Ok(out)
+        }
+        OperationType::Gate(gate) => Ok(gate.gate.clone_ref(py)),
+        OperationType::Instruction(inst) => Ok(inst.instruction.clone_ref(py)),
+        OperationType::Operation(op) => Ok(op.operation.clone_ref(py)),
+    }
+}
+
+/// A container struct that contains the output from the Python object to
+/// conversion to construct a CircuitInstruction object
+#[derive(Debug)]
+pub(crate) struct OperationTypeConstruct {
+    pub operation: OperationType,
+    pub params: SmallVec<[Param; 3]>,
+    pub label: Option,
+    pub duration: Option,
+    pub unit: Option,
+    pub condition: Option,
+}
+
+/// Convert an inbound Python object for a Qiskit operation and build a rust
+/// representation of that operation. This will map it to appropriate variant
+/// of operation type based on class
+pub(crate) fn convert_py_to_operation_type(
+    py: Python,
+    py_op: PyObject,
+) -> PyResult {
+    let attr = intern!(py, "_standard_gate");
+    let py_op_bound = py_op.clone_ref(py).into_bound(py);
+    // Get PyType from either base_class if it exists, or if not use the
+    // class/type info from the pyobject
+    let binding = py_op_bound.getattr(intern!(py, "base_class")).ok();
+    let op_obj = py_op_bound.get_type();
+    let raw_op_type: Py = match binding {
+        Some(base_class) => base_class.downcast()?.clone().unbind(),
+        None => op_obj.unbind(),
+    };
+    let op_type: Bound = raw_op_type.into_bound(py);
+    let mut standard: Option = match op_type.getattr(attr) {
+        Ok(stdgate) => match stdgate.extract().ok() {
+            Some(gate) => gate,
+            None => None,
+        },
+        Err(_) => None,
+    };
+    // If the input instruction is a standard gate and a singleton instance
+    // we should check for mutable state. A mutable instance should be treated
+    // as a custom gate not a standard gate because it has custom properties.
+    //
+    // In the futuer we can revisit this when we've dropped `duration`, `unit`,
+    // and `condition` from the api as we should own the label in the
+    // `CircuitInstruction`. The other piece here is for controlled gates there
+    // is the control state, so for `SingletonControlledGates` we'll still need
+    // this check.
+    if standard.is_some() {
+        let mutable: bool = py_op.getattr(py, intern!(py, "mutable"))?.extract(py)?;
+        if mutable
+            && (py_op_bound.is_instance(SINGLETON_GATE.get_bound(py))?
+                || py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?)
+        {
+            standard = None;
+        }
+    }
+    if let Some(op) = standard {
+        let base_class = op_type.to_object(py);
+        populate_std_gate_map(py, op, base_class);
+        return Ok(OperationTypeConstruct {
+            operation: OperationType::Standard(op),
+            params: py_op.getattr(py, intern!(py, "params"))?.extract(py)?,
+            label: py_op.getattr(py, intern!(py, "label"))?.extract(py)?,
+            duration: py_op.getattr(py, intern!(py, "duration"))?.extract(py)?,
+            unit: py_op.getattr(py, intern!(py, "unit"))?.extract(py)?,
+            condition: py_op.getattr(py, intern!(py, "condition"))?.extract(py)?,
+        });
+    }
+    if op_type.is_subclass(GATE.get_bound(py))? {
+        let params = py_op.getattr(py, intern!(py, "params"))?.extract(py)?;
+        let label = py_op.getattr(py, intern!(py, "label"))?.extract(py)?;
+        let duration = py_op.getattr(py, intern!(py, "duration"))?.extract(py)?;
+        let unit = py_op.getattr(py, intern!(py, "unit"))?.extract(py)?;
+        let condition = py_op.getattr(py, intern!(py, "condition"))?.extract(py)?;
+
+        let out_op = PyGate {
+            qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
+            clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
+            params: py_op
+                .getattr(py, intern!(py, "params"))?
+                .downcast_bound::(py)?
+                .len() as u32,
+            op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
+            gate: py_op,
+        };
+        return Ok(OperationTypeConstruct {
+            operation: OperationType::Gate(out_op),
+            params,
+            label,
+            duration,
+            unit,
+            condition,
+        });
+    }
+    if op_type.is_subclass(INSTRUCTION.get_bound(py))? {
+        let params = py_op.getattr(py, intern!(py, "params"))?.extract(py)?;
+        let label = py_op.getattr(py, intern!(py, "label"))?.extract(py)?;
+        let duration = py_op.getattr(py, intern!(py, "duration"))?.extract(py)?;
+        let unit = py_op.getattr(py, intern!(py, "unit"))?.extract(py)?;
+        let condition = py_op.getattr(py, intern!(py, "condition"))?.extract(py)?;
+
+        let out_op = PyInstruction {
+            qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
+            clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
+            params: py_op
+                .getattr(py, intern!(py, "params"))?
+                .downcast_bound::(py)?
+                .len() as u32,
+            op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
+            instruction: py_op,
+        };
+        return Ok(OperationTypeConstruct {
+            operation: OperationType::Instruction(out_op),
+            params,
+            label,
+            duration,
+            unit,
+            condition,
+        });
+    }
+
+    if op_type.is_subclass(OPERATION.get_bound(py))? {
+        let params = match py_op.getattr(py, intern!(py, "params")) {
+            Ok(value) => value.extract(py)?,
+            Err(_) => smallvec![],
+        };
+        let label = None;
+        let duration = None;
+        let unit = None;
+        let condition = None;
+        let out_op = PyOperation {
+            qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
+            clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
+            params: match py_op.getattr(py, intern!(py, "params")) {
+                Ok(value) => value.downcast_bound::(py)?.len() as u32,
+                Err(_) => 0,
+            },
+            op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
+            operation: py_op,
+        };
+        return Ok(OperationTypeConstruct {
+            operation: OperationType::Operation(out_op),
+            params,
+            label,
+            duration,
+            unit,
+            condition,
+        });
+    }
+    Err(PyValueError::new_err(format!("Invalid input: {}", py_op)))
+}
diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs
index c766461bb510..c8b6a4c8b082 100644
--- a/crates/circuit/src/dag_node.rs
+++ b/crates/circuit/src/dag_node.rs
@@ -10,7 +10,11 @@
 // 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 crate::circuit_instruction::{
+    convert_py_to_operation_type, operation_type_to_py, CircuitInstruction,
+    ExtraInstructionAttributes,
+};
+use crate::operations::Operation;
 use pyo3::prelude::*;
 use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple};
 use pyo3::{intern, PyObject, PyResult};
@@ -106,13 +110,33 @@ impl DAGOpNode {
             }
             None => qargs.str()?.into_any(),
         };
+        let res = convert_py_to_operation_type(py, op.clone_ref(py))?;
+
+        let extra_attrs = if res.label.is_some()
+            || res.duration.is_some()
+            || res.unit.is_some()
+            || res.condition.is_some()
+        {
+            Some(Box::new(ExtraInstructionAttributes {
+                label: res.label,
+                duration: res.duration,
+                unit: res.unit,
+                condition: res.condition,
+            }))
+        } else {
+            None
+        };
 
         Ok((
             DAGOpNode {
                 instruction: CircuitInstruction {
-                    operation: op,
+                    operation: res.operation,
                     qubits: qargs.unbind(),
                     clbits: cargs.unbind(),
+                    params: res.params,
+                    extra_attrs,
+                    #[cfg(feature = "cache_pygates")]
+                    py_op: Some(op),
                 },
                 sort_key: sort_key.unbind(),
             },
@@ -120,18 +144,18 @@ impl DAGOpNode {
         ))
     }
 
-    fn __reduce__(slf: PyRef, py: Python) -> PyObject {
+    fn __reduce__(slf: PyRef, py: Python) -> PyResult {
         let state = (slf.as_ref()._node_id, &slf.sort_key);
-        (
+        Ok((
             py.get_type_bound::(),
             (
-                &slf.instruction.operation,
+                operation_type_to_py(py, &slf.instruction)?,
                 &slf.instruction.qubits,
                 &slf.instruction.clbits,
             ),
             state,
         )
-            .into_py(py)
+            .into_py(py))
     }
 
     fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> {
@@ -142,13 +166,31 @@ impl DAGOpNode {
     }
 
     #[getter]
-    fn get_op(&self, py: Python) -> PyObject {
-        self.instruction.operation.clone_ref(py)
+    fn get_op(&self, py: Python) -> PyResult {
+        operation_type_to_py(py, &self.instruction)
     }
 
     #[setter]
-    fn set_op(&mut self, op: PyObject) {
-        self.instruction.operation = op;
+    fn set_op(&mut self, py: Python, op: PyObject) -> PyResult<()> {
+        let res = convert_py_to_operation_type(py, op)?;
+        self.instruction.operation = res.operation;
+        self.instruction.params = res.params;
+        let extra_attrs = if res.label.is_some()
+            || res.duration.is_some()
+            || res.unit.is_some()
+            || res.condition.is_some()
+        {
+            Some(Box::new(ExtraInstructionAttributes {
+                label: res.label,
+                duration: res.duration,
+                unit: res.unit,
+                condition: res.condition,
+            }))
+        } else {
+            None
+        };
+        self.instruction.extra_attrs = extra_attrs;
+        Ok(())
     }
 
     #[getter]
@@ -173,29 +215,27 @@ impl DAGOpNode {
 
     /// 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())
+    fn get_name(&self, py: Python) -> PyObject {
+        self.instruction.operation.name().to_object(py)
     }
 
     /// 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)
+    fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> {
+        let op = operation_type_to_py(py, &self.instruction)?;
+        op.bind(py).setattr(intern!(py, "name"), new_name)?;
+        let res = convert_py_to_operation_type(py, op)?;
+        self.instruction.operation = res.operation;
+        Ok(())
     }
 
     /// Returns a representation of the DAGOpNode
     fn __repr__(&self, py: Python) -> PyResult {
         Ok(format!(
             "DAGOpNode(op={}, qargs={}, cargs={})",
-            self.instruction.operation.bind(py).repr()?,
+            operation_type_to_py(py, &self.instruction)?
+                .bind(py)
+                .repr()?,
             self.instruction.qubits.bind(py).repr()?,
             self.instruction.clbits.bind(py).repr()?
         ))
diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs
new file mode 100644
index 000000000000..72e1087637c0
--- /dev/null
+++ b/crates/circuit/src/gate_matrix.rs
@@ -0,0 +1,224 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2023
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+use num_complex::Complex64;
+use std::f64::consts::FRAC_1_SQRT_2;
+
+// num-complex exposes an equivalent function but it's not a const function
+// so it's not compatible with static definitions. This is a const func and
+// just reduces the amount of typing we need.
+#[inline(always)]
+const fn c64(re: f64, im: f64) -> Complex64 {
+    Complex64::new(re, im)
+}
+
+pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] =
+    [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]];
+
+#[inline]
+pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] {
+    let half_theta = theta / 2.;
+    let cos = c64(half_theta.cos(), 0.);
+    let isin = c64(0., -half_theta.sin());
+    [[cos, isin], [isin, cos]]
+}
+
+#[inline]
+pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] {
+    let half_theta = theta / 2.;
+    let cos = c64(half_theta.cos(), 0.);
+    let sin = c64(half_theta.sin(), 0.);
+    [[cos, -sin], [sin, cos]]
+}
+
+#[inline]
+pub fn rz_gate(theta: f64) -> [[Complex64; 2]; 2] {
+    let ilam2 = c64(0., 0.5 * theta);
+    [[(-ilam2).exp(), c64(0., 0.)], [c64(0., 0.), ilam2.exp()]]
+}
+
+pub static H_GATE: [[Complex64; 2]; 2] = [
+    [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)],
+    [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)],
+];
+
+pub static CX_GATE: [[Complex64; 4]; 4] = [
+    [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
+];
+
+pub static SX_GATE: [[Complex64; 2]; 2] = [
+    [c64(0.5, 0.5), c64(0.5, -0.5)],
+    [c64(0.5, -0.5), c64(0.5, 0.5)],
+];
+
+pub static X_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(1., 0.)], [c64(1., 0.), c64(0., 0.)]];
+
+pub static Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]];
+
+pub static Y_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(0., -1.)], [c64(0., 1.), c64(0., 0.)]];
+
+pub static CZ_GATE: [[Complex64; 4]; 4] = [
+    [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(-1., 0.)],
+];
+
+pub static CY_GATE: [[Complex64; 4]; 4] = [
+    [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(0., -1.)],
+    [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)],
+];
+
+pub static CCX_GATE: [[Complex64; 8]; 8] = [
+    [
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(1., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+        c64(0., 0.),
+    ],
+];
+
+pub static ECR_GATE: [[Complex64; 4]; 4] = [
+    [
+        c64(0., 0.),
+        c64(FRAC_1_SQRT_2, 0.),
+        c64(0., 0.),
+        c64(0., FRAC_1_SQRT_2),
+    ],
+    [
+        c64(FRAC_1_SQRT_2, 0.),
+        c64(0., 0.),
+        c64(0., -FRAC_1_SQRT_2),
+        c64(0., 0.),
+    ],
+    [
+        c64(0., 0.),
+        c64(0., FRAC_1_SQRT_2),
+        c64(0., 0.),
+        c64(FRAC_1_SQRT_2, 0.),
+    ],
+    [
+        c64(0., -FRAC_1_SQRT_2),
+        c64(0., 0.),
+        c64(FRAC_1_SQRT_2, 0.),
+        c64(0., 0.),
+    ],
+];
+
+pub static SWAP_GATE: [[Complex64; 4]; 4] = [
+    [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
+];
+
+#[inline]
+pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] {
+    [[c64(0., theta).exp()]]
+}
+
+#[inline]
+pub fn phase_gate(lam: f64) -> [[Complex64; 2]; 2] {
+    [
+        [c64(1., 0.), c64(0., 0.)],
+        [c64(0., 0.), c64(0., lam).exp()],
+    ]
+}
+
+#[inline]
+pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
+    let cos = (theta / 2.).cos();
+    let sin = (theta / 2.).sin();
+    [
+        [c64(cos, 0.), (-c64(0., lam).exp()) * sin],
+        [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos],
+    ]
+}
diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs
new file mode 100644
index 000000000000..050f7f2e053c
--- /dev/null
+++ b/crates/circuit/src/imports.rs
@@ -0,0 +1,168 @@
+// 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.
+
+// This module contains objects imported from Python that are reused. These are
+// typically data model classes that are used to identify an object, or for
+// python side casting
+
+use pyo3::prelude::*;
+use pyo3::sync::GILOnceCell;
+
+use crate::operations::{StandardGate, STANDARD_GATE_SIZE};
+
+/// Helper wrapper around `GILOnceCell` instances that are just intended to store a Python object
+/// that is lazily imported.
+pub struct ImportOnceCell {
+    module: &'static str,
+    object: &'static str,
+    cell: GILOnceCell>,
+}
+
+impl ImportOnceCell {
+    const fn new(module: &'static str, object: &'static str) -> Self {
+        Self {
+            module,
+            object,
+            cell: GILOnceCell::new(),
+        }
+    }
+
+    /// Get the underlying GIL-independent reference to the contained object, importing if
+    /// required.
+    #[inline]
+    pub fn get(&self, py: Python) -> &Py {
+        self.cell.get_or_init(py, || {
+            py.import_bound(self.module)
+                .unwrap()
+                .getattr(self.object)
+                .unwrap()
+                .unbind()
+        })
+    }
+
+    /// Get a GIL-bound reference to the contained object, importing if required.
+    #[inline]
+    pub fn get_bound<'py>(&self, py: Python<'py>) -> &Bound<'py, PyAny> {
+        self.get(py).bind(py)
+    }
+}
+
+pub static BUILTIN_LIST: ImportOnceCell = ImportOnceCell::new("builtins", "list");
+pub static OPERATION: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.operation", "Operation");
+pub static INSTRUCTION: ImportOnceCell =
+    ImportOnceCell::new("qiskit.circuit.instruction", "Instruction");
+pub static GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.gate", "Gate");
+pub static QUBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.quantumregister", "Qubit");
+pub static CLBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.classicalregister", "Clbit");
+pub static PARAMETER_EXPRESSION: ImportOnceCell =
+    ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression");
+pub static QUANTUM_CIRCUIT: ImportOnceCell =
+    ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit");
+pub static SINGLETON_GATE: ImportOnceCell =
+    ImportOnceCell::new("qiskit.circuit.singleton", "SingletonGate");
+pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell =
+    ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate");
+
+/// A mapping from the enum variant in crate::operations::StandardGate to the python
+/// module path and class name to import it. This is used to populate the conversion table
+/// when a gate is added directly via the StandardGate path and there isn't a Python object
+/// to poll the _standard_gate attribute for.
+///
+/// NOTE: the order here is significant it must match the StandardGate variant's number must match
+/// index of it's entry in this table. This is all done statically for performance
+static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
+    // ZGate = 0
+    ["qiskit.circuit.library.standard_gates.z", "ZGate"],
+    // YGate = 1
+    ["qiskit.circuit.library.standard_gates.y", "YGate"],
+    // XGate = 2
+    ["qiskit.circuit.library.standard_gates.x", "XGate"],
+    // CZGate = 3
+    ["qiskit.circuit.library.standard_gates.z", "CZGate"],
+    // CYGate = 4
+    ["qiskit.circuit.library.standard_gates.y", "CYGate"],
+    // CXGate = 5
+    ["qiskit.circuit.library.standard_gates.x", "CXGate"],
+    // CCXGate = 6
+    ["qiskit.circuit.library.standard_gates.x", "CCXGate"],
+    // RXGate = 7
+    ["qiskit.circuit.library.standard_gates.rx", "RXGate"],
+    // RYGate = 8
+    ["qiskit.circuit.library.standard_gates.ry", "RYGate"],
+    // RZGate = 9
+    ["qiskit.circuit.library.standard_gates.rz", "RZGate"],
+    // ECRGate = 10
+    ["qiskit.circuit.library.standard_gates.ecr", "ECRGate"],
+    // SwapGate = 11
+    ["qiskit.circuit.library.standard_gates.swap", "SwapGate"],
+    // SXGate = 12
+    ["qiskit.circuit.library.standard_gates.sx", "SXGate"],
+    // GlobalPhaseGate = 13
+    [
+        "qiskit.circuit.library.standard_gates.global_phase",
+        "GlobalPhaseGate",
+    ],
+    // IGate = 14
+    ["qiskit.circuit.library.standard_gates.i", "IGate"],
+    // HGate = 15
+    ["qiskit.circuit.library.standard_gates.h", "HGate"],
+    // PhaseGate = 16
+    ["qiskit.circuit.library.standard_gates.p", "PhaseGate"],
+    // UGate = 17
+    ["qiskit.circuit.library.standard_gates.u", "UGate"],
+];
+
+/// A mapping from the enum variant in crate::operations::StandardGate to the python object for the
+/// class that matches it. This is typically used when we need to convert from the internal rust
+/// representation to a Python object for a python user to interact with.
+///
+/// NOTE: the order here is significant it must match the StandardGate variant's number must match
+/// index of it's entry in this table. This is all done statically for performance
+static mut STDGATE_PYTHON_GATES: GILOnceCell<[Option; STANDARD_GATE_SIZE]> =
+    GILOnceCell::new();
+
+#[inline]
+pub fn populate_std_gate_map(py: Python, rs_gate: StandardGate, py_gate: PyObject) {
+    let gate_map = unsafe {
+        match STDGATE_PYTHON_GATES.get_mut() {
+            Some(gate_map) => gate_map,
+            None => {
+                let array: [Option; STANDARD_GATE_SIZE] = std::array::from_fn(|_| None);
+                STDGATE_PYTHON_GATES.set(py, array).unwrap();
+                STDGATE_PYTHON_GATES.get_mut().unwrap()
+            }
+        }
+    };
+    let gate_cls = &gate_map[rs_gate as usize];
+    if gate_cls.is_none() {
+        gate_map[rs_gate as usize] = Some(py_gate.clone_ref(py));
+    }
+}
+
+#[inline]
+pub fn get_std_gate_class(py: Python, rs_gate: StandardGate) -> PyResult {
+    let gate_map =
+        unsafe { STDGATE_PYTHON_GATES.get_or_init(py, || std::array::from_fn(|_| None)) };
+    let gate = &gate_map[rs_gate as usize];
+    let populate = gate.is_none();
+    let out_gate = match gate {
+        Some(gate) => gate.clone_ref(py),
+        None => {
+            let [py_mod, py_class] = STDGATE_IMPORT_PATHS[rs_gate as usize];
+            py.import_bound(py_mod)?.getattr(py_class)?.unbind()
+        }
+    };
+    if populate {
+        populate_std_gate_map(py, rs_gate, out_gate.clone_ref(py));
+    }
+    Ok(out_gate)
+}
diff --git a/crates/circuit/src/interner.rs b/crates/circuit/src/interner.rs
index 426675702053..f22bb80ae052 100644
--- a/crates/circuit/src/interner.rs
+++ b/crates/circuit/src/interner.rs
@@ -10,11 +10,13 @@
 // copyright notice, and modified files need to carry a notice indicating
 // that they have been altered from the originals.
 
-use hashbrown::HashMap;
-use pyo3::{IntoPy, PyObject, Python};
 use std::hash::Hash;
 use std::sync::Arc;
 
+use hashbrown::HashMap;
+use pyo3::exceptions::PyRuntimeError;
+use pyo3::prelude::*;
+
 #[derive(Clone, Copy, Debug)]
 pub struct Index(u32);
 
@@ -42,6 +44,12 @@ impl IntoPy for Index {
 
 pub struct CacheFullError;
 
+impl From for PyErr {
+    fn from(_: CacheFullError) -> Self {
+        PyRuntimeError::new_err("The bit operands cache is full!")
+    }
+}
+
 /// An append-only data structure for interning generic
 /// Rust types.
 #[derive(Clone, Debug)]
diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs
index 90f2b7c7f070..d7f285911750 100644
--- a/crates/circuit/src/lib.rs
+++ b/crates/circuit/src/lib.rs
@@ -13,10 +13,13 @@
 pub mod circuit_data;
 pub mod circuit_instruction;
 pub mod dag_node;
+pub mod gate_matrix;
+pub mod imports;
+pub mod operations;
+pub mod parameter_table;
 
 mod bit_data;
 mod interner;
-mod packed_instruction;
 
 use pyo3::prelude::*;
 use pyo3::types::PySlice;
@@ -33,9 +36,9 @@ pub enum SliceOrInt<'a> {
 
 pub type BitType = u32;
 #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
-pub struct Qubit(BitType);
+pub struct Qubit(pub BitType);
 #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
-pub struct Clbit(BitType);
+pub struct Clbit(pub BitType);
 
 impl From for Qubit {
     fn from(value: BitType) -> Self {
@@ -69,5 +72,9 @@ pub fn circuit(m: Bound) -> PyResult<()> {
     m.add_class::()?;
     m.add_class::()?;
     m.add_class::()?;
+    m.add_class::()?;
+    m.add_class::()?;
+    m.add_class::()?;
+    m.add_class::()?;
     Ok(())
 }
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
new file mode 100644
index 000000000000..ead1b8ee1ebb
--- /dev/null
+++ b/crates/circuit/src/operations.rs
@@ -0,0 +1,786 @@
+// 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 std::f64::consts::PI;
+
+use crate::circuit_data::CircuitData;
+use crate::imports::{PARAMETER_EXPRESSION, QUANTUM_CIRCUIT};
+use crate::{gate_matrix, Qubit};
+
+use ndarray::{aview2, Array2};
+use num_complex::Complex64;
+use numpy::IntoPyArray;
+use numpy::PyReadonlyArray2;
+use pyo3::prelude::*;
+use pyo3::{intern, IntoPy, Python};
+use smallvec::smallvec;
+
+/// Valid types for an operation field in a CircuitInstruction
+///
+/// These are basically the types allowed in a QuantumCircuit
+#[derive(FromPyObject, Clone, Debug)]
+pub enum OperationType {
+    Standard(StandardGate),
+    Instruction(PyInstruction),
+    Gate(PyGate),
+    Operation(PyOperation),
+}
+
+impl Operation for OperationType {
+    fn name(&self) -> &str {
+        match self {
+            Self::Standard(op) => op.name(),
+            Self::Gate(op) => op.name(),
+            Self::Instruction(op) => op.name(),
+            Self::Operation(op) => op.name(),
+        }
+    }
+
+    fn num_qubits(&self) -> u32 {
+        match self {
+            Self::Standard(op) => op.num_qubits(),
+            Self::Gate(op) => op.num_qubits(),
+            Self::Instruction(op) => op.num_qubits(),
+            Self::Operation(op) => op.num_qubits(),
+        }
+    }
+    fn num_clbits(&self) -> u32 {
+        match self {
+            Self::Standard(op) => op.num_clbits(),
+            Self::Gate(op) => op.num_clbits(),
+            Self::Instruction(op) => op.num_clbits(),
+            Self::Operation(op) => op.num_clbits(),
+        }
+    }
+
+    fn num_params(&self) -> u32 {
+        match self {
+            Self::Standard(op) => op.num_params(),
+            Self::Gate(op) => op.num_params(),
+            Self::Instruction(op) => op.num_params(),
+            Self::Operation(op) => op.num_params(),
+        }
+    }
+    fn matrix(&self, params: &[Param]) -> Option> {
+        match self {
+            Self::Standard(op) => op.matrix(params),
+            Self::Gate(op) => op.matrix(params),
+            Self::Instruction(op) => op.matrix(params),
+            Self::Operation(op) => op.matrix(params),
+        }
+    }
+
+    fn control_flow(&self) -> bool {
+        match self {
+            Self::Standard(op) => op.control_flow(),
+            Self::Gate(op) => op.control_flow(),
+            Self::Instruction(op) => op.control_flow(),
+            Self::Operation(op) => op.control_flow(),
+        }
+    }
+
+    fn definition(&self, params: &[Param]) -> Option {
+        match self {
+            Self::Standard(op) => op.definition(params),
+            Self::Gate(op) => op.definition(params),
+            Self::Instruction(op) => op.definition(params),
+            Self::Operation(op) => op.definition(params),
+        }
+    }
+
+    fn standard_gate(&self) -> Option {
+        match self {
+            Self::Standard(op) => op.standard_gate(),
+            Self::Gate(op) => op.standard_gate(),
+            Self::Instruction(op) => op.standard_gate(),
+            Self::Operation(op) => op.standard_gate(),
+        }
+    }
+
+    fn directive(&self) -> bool {
+        match self {
+            Self::Standard(op) => op.directive(),
+            Self::Gate(op) => op.directive(),
+            Self::Instruction(op) => op.directive(),
+            Self::Operation(op) => op.directive(),
+        }
+    }
+}
+
+/// Trait for generic circuit operations these define the common attributes
+/// needed for something to be addable to the circuit struct
+pub trait Operation {
+    fn name(&self) -> &str;
+    fn num_qubits(&self) -> u32;
+    fn num_clbits(&self) -> u32;
+    fn num_params(&self) -> u32;
+    fn control_flow(&self) -> bool;
+    fn matrix(&self, params: &[Param]) -> Option>;
+    fn definition(&self, params: &[Param]) -> Option;
+    fn standard_gate(&self) -> Option;
+    fn directive(&self) -> bool;
+}
+
+#[derive(Clone, Debug)]
+pub enum Param {
+    ParameterExpression(PyObject),
+    Float(f64),
+    Obj(PyObject),
+}
+
+impl<'py> FromPyObject<'py> for Param {
+    fn extract_bound(b: &Bound<'py, PyAny>) -> Result {
+        Ok(
+            if b.is_instance(PARAMETER_EXPRESSION.get_bound(b.py()))?
+                || b.is_instance(QUANTUM_CIRCUIT.get_bound(b.py()))?
+            {
+                Param::ParameterExpression(b.clone().unbind())
+            } else if let Ok(val) = b.extract::() {
+                Param::Float(val)
+            } else {
+                Param::Obj(b.clone().unbind())
+            },
+        )
+    }
+}
+
+impl IntoPy for Param {
+    fn into_py(self, py: Python) -> PyObject {
+        match &self {
+            Self::Float(val) => val.to_object(py),
+            Self::ParameterExpression(val) => val.clone_ref(py),
+            Self::Obj(val) => val.clone_ref(py),
+        }
+    }
+}
+
+impl ToPyObject for Param {
+    fn to_object(&self, py: Python) -> PyObject {
+        match self {
+            Self::Float(val) => val.to_object(py),
+            Self::ParameterExpression(val) => val.clone_ref(py),
+            Self::Obj(val) => val.clone_ref(py),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)]
+#[pyclass(module = "qiskit._accelerate.circuit")]
+pub enum StandardGate {
+    ZGate = 0,
+    YGate = 1,
+    XGate = 2,
+    CZGate = 3,
+    CYGate = 4,
+    CXGate = 5,
+    CCXGate = 6,
+    RXGate = 7,
+    RYGate = 8,
+    RZGate = 9,
+    ECRGate = 10,
+    SwapGate = 11,
+    SXGate = 12,
+    GlobalPhaseGate = 13,
+    IGate = 14,
+    HGate = 15,
+    PhaseGate = 16,
+    UGate = 17,
+}
+
+static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] =
+    [1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1];
+
+static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] =
+    [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3];
+
+static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
+    "z",
+    "y",
+    "x",
+    "cz",
+    "cy",
+    "cx",
+    "ccx",
+    "rx",
+    "ry",
+    "rz",
+    "ecr",
+    "swap",
+    "sx",
+    "global_phase",
+    "id",
+    "h",
+    "p",
+    "u",
+];
+
+#[pymethods]
+impl StandardGate {
+    pub fn copy(&self) -> Self {
+        *self
+    }
+
+    // These pymethods are for testing:
+    pub fn _to_matrix(&self, py: Python, params: Vec) -> Option {
+        self.matrix(¶ms)
+            .map(|x| x.into_pyarray_bound(py).into())
+    }
+
+    pub fn _num_params(&self) -> u32 {
+        self.num_params()
+    }
+
+    pub fn _get_definition(&self, params: Vec) -> Option {
+        self.definition(¶ms)
+    }
+
+    #[getter]
+    pub fn get_num_qubits(&self) -> u32 {
+        self.num_qubits()
+    }
+
+    #[getter]
+    pub fn get_num_clbits(&self) -> u32 {
+        self.num_clbits()
+    }
+
+    #[getter]
+    pub fn get_num_params(&self) -> u32 {
+        self.num_params()
+    }
+
+    #[getter]
+    pub fn get_name(&self) -> &str {
+        self.name()
+    }
+}
+
+// This must be kept up-to-date with `StandardGate` when adding or removing
+// gates from the enum
+//
+// Remove this when std::mem::variant_count() is stabilized (see
+// https://github.com/rust-lang/rust/issues/73662 )
+pub const STANDARD_GATE_SIZE: usize = 18;
+
+impl Operation for StandardGate {
+    fn name(&self) -> &str {
+        STANDARD_GATE_NAME[*self as usize]
+    }
+
+    fn num_qubits(&self) -> u32 {
+        STANDARD_GATE_NUM_QUBITS[*self as usize]
+    }
+
+    fn num_params(&self) -> u32 {
+        STANDARD_GATE_NUM_PARAMS[*self as usize]
+    }
+
+    fn num_clbits(&self) -> u32 {
+        0
+    }
+
+    fn control_flow(&self) -> bool {
+        false
+    }
+
+    fn directive(&self) -> bool {
+        false
+    }
+
+    fn matrix(&self, params: &[Param]) -> Option> {
+        match self {
+            Self::ZGate => match params {
+                [] => Some(aview2(&gate_matrix::Z_GATE).to_owned()),
+                _ => None,
+            },
+            Self::YGate => match params {
+                [] => Some(aview2(&gate_matrix::Y_GATE).to_owned()),
+                _ => None,
+            },
+            Self::XGate => match params {
+                [] => Some(aview2(&gate_matrix::X_GATE).to_owned()),
+                _ => None,
+            },
+            Self::CZGate => match params {
+                [] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()),
+                _ => None,
+            },
+            Self::CYGate => match params {
+                [] => Some(aview2(&gate_matrix::CY_GATE).to_owned()),
+                _ => None,
+            },
+            Self::CXGate => match params {
+                [] => Some(aview2(&gate_matrix::CX_GATE).to_owned()),
+                _ => None,
+            },
+            Self::CCXGate => match params {
+                [] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()),
+                _ => None,
+            },
+            Self::RXGate => match params {
+                [Param::Float(theta)] => Some(aview2(&gate_matrix::rx_gate(*theta)).to_owned()),
+                _ => None,
+            },
+            Self::RYGate => match params {
+                [Param::Float(theta)] => Some(aview2(&gate_matrix::ry_gate(*theta)).to_owned()),
+                _ => None,
+            },
+            Self::RZGate => match params {
+                [Param::Float(theta)] => Some(aview2(&gate_matrix::rz_gate(*theta)).to_owned()),
+                _ => None,
+            },
+            Self::ECRGate => match params {
+                [] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()),
+                _ => None,
+            },
+            Self::SwapGate => match params {
+                [] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()),
+                _ => None,
+            },
+            Self::SXGate => match params {
+                [] => Some(aview2(&gate_matrix::SX_GATE).to_owned()),
+                _ => None,
+            },
+            Self::GlobalPhaseGate => match params {
+                [Param::Float(theta)] => {
+                    Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned())
+                }
+                _ => None,
+            },
+            Self::IGate => match params {
+                [] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()),
+                _ => None,
+            },
+            Self::HGate => match params {
+                [] => Some(aview2(&gate_matrix::H_GATE).to_owned()),
+                _ => None,
+            },
+            Self::PhaseGate => match params {
+                [Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()),
+                _ => None,
+            },
+            Self::UGate => match params {
+                [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => {
+                    Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned())
+                }
+                _ => None,
+            },
+        }
+    }
+
+    fn definition(&self, params: &[Param]) -> Option {
+        match self {
+            Self::ZGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::PhaseGate,
+                            smallvec![Param::Float(PI)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::YGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::UGate,
+                            smallvec![
+                                Param::Float(PI),
+                                Param::Float(PI / 2.),
+                                Param::Float(PI / 2.),
+                            ],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::XGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::UGate,
+                            smallvec![Param::Float(PI), Param::Float(0.), Param::Float(PI)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::CZGate => Python::with_gil(|py| -> Option {
+                let q1 = smallvec![Qubit(1)];
+                let q0_1 = smallvec![Qubit(0), Qubit(1)];
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        2,
+                        [
+                            (Self::HGate, smallvec![], q1.clone()),
+                            (Self::CXGate, smallvec![], q0_1),
+                            (Self::HGate, smallvec![], q1),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::CYGate => todo!("Add when we have S and S dagger"),
+            Self::CXGate => None,
+            Self::CCXGate => todo!("Add when we have T and TDagger"),
+            Self::RXGate => todo!("Add when we have R"),
+            Self::RYGate => todo!("Add when we have R"),
+            Self::RZGate => Python::with_gil(|py| -> Option {
+                match ¶ms[0] {
+                    Param::Float(theta) => Some(
+                        CircuitData::from_standard_gates(
+                            py,
+                            1,
+                            [(
+                                Self::PhaseGate,
+                                smallvec![Param::Float(*theta)],
+                                smallvec![Qubit(0)],
+                            )],
+                            Param::Float(-0.5 * theta),
+                        )
+                        .expect("Unexpected Qiskit python bug"),
+                    ),
+                    Param::ParameterExpression(theta) => Some(
+                        CircuitData::from_standard_gates(
+                            py,
+                            1,
+                            [(
+                                Self::PhaseGate,
+                                smallvec![Param::ParameterExpression(theta.clone_ref(py))],
+                                smallvec![Qubit(0)],
+                            )],
+                            Param::ParameterExpression(
+                                theta
+                                    .call_method1(py, intern!(py, "__rmul__"), (-0.5,))
+                                    .expect("Parameter expression for global phase failed"),
+                            ),
+                        )
+                        .expect("Unexpected Qiskit python bug"),
+                    ),
+                    Param::Obj(_) => unreachable!(),
+                }
+            }),
+            Self::ECRGate => todo!("Add when we have RZX"),
+            Self::SwapGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        2,
+                        [
+                            (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
+                            (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]),
+                            (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::SXGate => todo!("Add when we have S dagger"),
+            Self::GlobalPhaseGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(py, 0, [], params[0].clone())
+                        .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::IGate => None,
+            Self::HGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::UGate,
+                            smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::PhaseGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::UGate,
+                            smallvec![Param::Float(0.), Param::Float(0.), params[0].clone()],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::UGate => None,
+        }
+    }
+
+    fn standard_gate(&self) -> Option {
+        Some(*self)
+    }
+}
+
+const FLOAT_ZERO: Param = Param::Float(0.0);
+
+/// This class is used to wrap a Python side Instruction that is not in the standard library
+#[derive(Clone, Debug)]
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub struct PyInstruction {
+    pub qubits: u32,
+    pub clbits: u32,
+    pub params: u32,
+    pub op_name: String,
+    pub instruction: PyObject,
+}
+
+#[pymethods]
+impl PyInstruction {
+    #[new]
+    fn new(op_name: String, qubits: u32, clbits: u32, params: u32, instruction: PyObject) -> Self {
+        PyInstruction {
+            qubits,
+            clbits,
+            params,
+            op_name,
+            instruction,
+        }
+    }
+}
+
+impl Operation for PyInstruction {
+    fn name(&self) -> &str {
+        self.op_name.as_str()
+    }
+    fn num_qubits(&self) -> u32 {
+        self.qubits
+    }
+    fn num_clbits(&self) -> u32 {
+        self.clbits
+    }
+    fn num_params(&self) -> u32 {
+        self.params
+    }
+    fn control_flow(&self) -> bool {
+        false
+    }
+    fn matrix(&self, _params: &[Param]) -> Option> {
+        None
+    }
+    fn definition(&self, _params: &[Param]) -> Option {
+        Python::with_gil(|py| -> Option {
+            match self.instruction.getattr(py, intern!(py, "definition")) {
+                Ok(definition) => {
+                    let res: Option = definition.call0(py).ok()?.extract(py).ok();
+                    match res {
+                        Some(x) => {
+                            let out: CircuitData =
+                                x.getattr(py, intern!(py, "data")).ok()?.extract(py).ok()?;
+                            Some(out)
+                        }
+                        None => None,
+                    }
+                }
+                Err(_) => None,
+            }
+        })
+    }
+    fn standard_gate(&self) -> Option {
+        None
+    }
+
+    fn directive(&self) -> bool {
+        Python::with_gil(|py| -> bool {
+            match self.instruction.getattr(py, intern!(py, "_directive")) {
+                Ok(directive) => {
+                    let res: bool = directive.extract(py).unwrap();
+                    res
+                }
+                Err(_) => false,
+            }
+        })
+    }
+}
+
+/// This class is used to wrap a Python side Gate that is not in the standard library
+#[derive(Clone, Debug)]
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub struct PyGate {
+    pub qubits: u32,
+    pub clbits: u32,
+    pub params: u32,
+    pub op_name: String,
+    pub gate: PyObject,
+}
+
+#[pymethods]
+impl PyGate {
+    #[new]
+    fn new(op_name: String, qubits: u32, clbits: u32, params: u32, gate: PyObject) -> Self {
+        PyGate {
+            qubits,
+            clbits,
+            params,
+            op_name,
+            gate,
+        }
+    }
+}
+
+impl Operation for PyGate {
+    fn name(&self) -> &str {
+        self.op_name.as_str()
+    }
+    fn num_qubits(&self) -> u32 {
+        self.qubits
+    }
+    fn num_clbits(&self) -> u32 {
+        self.clbits
+    }
+    fn num_params(&self) -> u32 {
+        self.params
+    }
+    fn control_flow(&self) -> bool {
+        false
+    }
+    fn matrix(&self, _params: &[Param]) -> Option> {
+        Python::with_gil(|py| -> Option> {
+            match self.gate.getattr(py, intern!(py, "to_matrix")) {
+                Ok(to_matrix) => {
+                    let res: Option = to_matrix.call0(py).ok()?.extract(py).ok();
+                    match res {
+                        Some(x) => {
+                            let array: PyReadonlyArray2 = x.extract(py).ok()?;
+                            Some(array.as_array().to_owned())
+                        }
+                        None => None,
+                    }
+                }
+                Err(_) => None,
+            }
+        })
+    }
+    fn definition(&self, _params: &[Param]) -> Option {
+        Python::with_gil(|py| -> Option {
+            match self.gate.getattr(py, intern!(py, "definition")) {
+                Ok(definition) => {
+                    let res: Option = definition.call0(py).ok()?.extract(py).ok();
+                    match res {
+                        Some(x) => {
+                            let out: CircuitData =
+                                x.getattr(py, intern!(py, "data")).ok()?.extract(py).ok()?;
+                            Some(out)
+                        }
+                        None => None,
+                    }
+                }
+                Err(_) => None,
+            }
+        })
+    }
+    fn standard_gate(&self) -> Option {
+        Python::with_gil(|py| -> Option {
+            match self.gate.getattr(py, intern!(py, "_standard_gate")) {
+                Ok(stdgate) => match stdgate.extract(py) {
+                    Ok(out_gate) => out_gate,
+                    Err(_) => None,
+                },
+                Err(_) => None,
+            }
+        })
+    }
+    fn directive(&self) -> bool {
+        false
+    }
+}
+
+/// This class is used to wrap a Python side Operation that is not in the standard library
+#[derive(Clone, Debug)]
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub struct PyOperation {
+    pub qubits: u32,
+    pub clbits: u32,
+    pub params: u32,
+    pub op_name: String,
+    pub operation: PyObject,
+}
+
+#[pymethods]
+impl PyOperation {
+    #[new]
+    fn new(op_name: String, qubits: u32, clbits: u32, params: u32, operation: PyObject) -> Self {
+        PyOperation {
+            qubits,
+            clbits,
+            params,
+            op_name,
+            operation,
+        }
+    }
+}
+
+impl Operation for PyOperation {
+    fn name(&self) -> &str {
+        self.op_name.as_str()
+    }
+    fn num_qubits(&self) -> u32 {
+        self.qubits
+    }
+    fn num_clbits(&self) -> u32 {
+        self.clbits
+    }
+    fn num_params(&self) -> u32 {
+        self.params
+    }
+    fn control_flow(&self) -> bool {
+        false
+    }
+    fn matrix(&self, _params: &[Param]) -> Option> {
+        None
+    }
+    fn definition(&self, _params: &[Param]) -> Option {
+        None
+    }
+    fn standard_gate(&self) -> Option {
+        None
+    }
+
+    fn directive(&self) -> bool {
+        Python::with_gil(|py| -> bool {
+            match self.operation.getattr(py, intern!(py, "_directive")) {
+                Ok(directive) => {
+                    let res: bool = directive.extract(py).unwrap();
+                    res
+                }
+                Err(_) => false,
+            }
+        })
+    }
+}
diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs
deleted file mode 100644
index 0c793f2b6408..000000000000
--- a/crates/circuit/src/packed_instruction.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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::interner::Index;
-use pyo3::prelude::*;
-
-/// Private type used to store instructions with interned arg lists.
-#[derive(Clone, Debug)]
-pub(crate) struct PackedInstruction {
-    /// The Python-side operation instance.
-    pub op: PyObject,
-    /// The index under which the interner has stored `qubits`.
-    pub qubits_id: Index,
-    /// The index under which the interner has stored `clbits`.
-    pub clbits_id: Index,
-}
diff --git a/crates/circuit/src/parameter_table.rs b/crates/circuit/src/parameter_table.rs
new file mode 100644
index 000000000000..48c779eed3a3
--- /dev/null
+++ b/crates/circuit/src/parameter_table.rs
@@ -0,0 +1,173 @@
+// This code is part of Qiskit.
+//
+// (C) Copyright IBM 2024
+//
+// This code is licensed under the Apache License, Version 2.0. You may
+// obtain a copy of this license in the LICENSE.txt file in the root directory
+// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+//
+// Any modifications or derivative works of this code must retain this
+// copyright notice, and modified files need to carry a notice indicating
+// that they have been altered from the originals.
+
+use pyo3::prelude::*;
+use pyo3::{import_exception, intern, PyObject};
+
+import_exception!(qiskit.circuit.exceptions, CircuitError);
+
+use hashbrown::{HashMap, HashSet};
+
+/// The index value in a `ParamEntry` that indicates the global phase.
+pub const GLOBAL_PHASE_INDEX: usize = usize::MAX;
+
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub(crate) struct ParamEntryKeys {
+    keys: Vec<(usize, usize)>,
+    iter_pos: usize,
+}
+
+#[pymethods]
+impl ParamEntryKeys {
+    fn __iter__(slf: PyRef) -> Py {
+        slf.into()
+    }
+
+    fn __next__(mut slf: PyRefMut) -> Option<(usize, usize)> {
+        if slf.iter_pos < slf.keys.len() {
+            let res = Some(slf.keys[slf.iter_pos]);
+            slf.iter_pos += 1;
+            res
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub(crate) struct ParamEntry {
+    /// Mapping of tuple of instruction index (in CircuitData) and parameter index to the actual
+    /// parameter object
+    pub index_ids: HashSet<(usize, usize)>,
+}
+
+impl ParamEntry {
+    pub fn add(&mut self, inst_index: usize, param_index: usize) {
+        self.index_ids.insert((inst_index, param_index));
+    }
+
+    pub fn discard(&mut self, inst_index: usize, param_index: usize) {
+        self.index_ids.remove(&(inst_index, param_index));
+    }
+}
+
+#[pymethods]
+impl ParamEntry {
+    #[new]
+    pub fn new(inst_index: usize, param_index: usize) -> Self {
+        ParamEntry {
+            index_ids: HashSet::from([(inst_index, param_index)]),
+        }
+    }
+
+    pub fn __len__(&self) -> usize {
+        self.index_ids.len()
+    }
+
+    pub fn __contains__(&self, key: (usize, usize)) -> bool {
+        self.index_ids.contains(&key)
+    }
+
+    pub fn __iter__(&self) -> ParamEntryKeys {
+        ParamEntryKeys {
+            keys: self.index_ids.iter().copied().collect(),
+            iter_pos: 0,
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
+pub(crate) struct ParamTable {
+    /// Mapping of parameter uuid (as an int) to the Parameter Entry
+    pub table: HashMap,
+    /// Mapping of parameter name to uuid as an int
+    pub names: HashMap,
+    /// Mapping of uuid to a parameter object
+    pub uuid_map: HashMap,
+}
+
+impl ParamTable {
+    pub fn insert(&mut self, py: Python, parameter: PyObject, entry: ParamEntry) -> PyResult<()> {
+        let uuid: u128 = parameter
+            .getattr(py, intern!(py, "_uuid"))?
+            .getattr(py, intern!(py, "int"))?
+            .extract(py)?;
+        let name: String = parameter.getattr(py, intern!(py, "name"))?.extract(py)?;
+
+        if self.names.contains_key(&name) && !self.table.contains_key(&uuid) {
+            return Err(CircuitError::new_err(format!(
+                "Name conflict on adding parameter: {}",
+                name
+            )));
+        }
+        self.table.insert(uuid, entry);
+        self.names.insert(name, uuid);
+        self.uuid_map.insert(uuid, parameter);
+        Ok(())
+    }
+
+    pub fn discard_references(
+        &mut self,
+        uuid: u128,
+        inst_index: usize,
+        param_index: usize,
+        name: String,
+    ) {
+        if let Some(refs) = self.table.get_mut(&uuid) {
+            if refs.__len__() == 1 {
+                self.table.remove(&uuid);
+                self.names.remove(&name);
+                self.uuid_map.remove(&uuid);
+            } else {
+                refs.discard(inst_index, param_index);
+            }
+        }
+    }
+}
+
+#[pymethods]
+impl ParamTable {
+    #[new]
+    pub fn new() -> Self {
+        ParamTable {
+            table: HashMap::new(),
+            names: HashMap::new(),
+            uuid_map: HashMap::new(),
+        }
+    }
+
+    pub fn clear(&mut self) {
+        self.table.clear();
+        self.names.clear();
+        self.uuid_map.clear();
+    }
+
+    pub fn pop(&mut self, key: u128, name: String) -> Option {
+        self.names.remove(&name);
+        self.uuid_map.remove(&key);
+        self.table.remove(&key)
+    }
+
+    fn set(&mut self, uuid: u128, name: String, param: PyObject, refs: ParamEntry) {
+        self.names.insert(name, uuid);
+        self.table.insert(uuid, refs);
+        self.uuid_map.insert(uuid, param);
+    }
+
+    pub fn get_param_from_name(&self, py: Python, name: String) -> Option {
+        self.names
+            .get(&name)
+            .map(|x| self.uuid_map.get(x).map(|y| y.clone_ref(py)))?
+    }
+}
diff --git a/crates/pyext/Cargo.toml b/crates/pyext/Cargo.toml
index daaf19e1f6a4..413165e84b1f 100644
--- a/crates/pyext/Cargo.toml
+++ b/crates/pyext/Cargo.toml
@@ -17,6 +17,7 @@ crate-type = ["cdylib"]
 # crates as standalone binaries, executables, we need `libpython` to be linked in, so we make the
 # feature a default, and run `cargo test --no-default-features` to turn it off.
 default = ["pyo3/extension-module"]
+cache_pygates = ["pyo3/extension-module", "qiskit-circuit/cache_pygates"]
 
 [dependencies]
 pyo3.workspace = true
diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py
index c6c95d27f924..bb0a30ea6af6 100644
--- a/qiskit/circuit/controlflow/builder.py
+++ b/qiskit/circuit/controlflow/builder.py
@@ -57,7 +57,9 @@ def instructions(self) -> Sequence[CircuitInstruction]:
         """Indexable view onto the :class:`.CircuitInstruction`s backing this scope."""
 
     @abc.abstractmethod
-    def append(self, instruction: CircuitInstruction) -> CircuitInstruction:
+    def append(
+        self, instruction: CircuitInstruction, *, _standard_gate=False
+    ) -> CircuitInstruction:
         """Low-level 'append' primitive; this may assume that the qubits, clbits and operation are
         all valid for the circuit.
 
@@ -420,7 +422,9 @@ def _raise_on_jump(operation):
                 " because it is not in a loop."
             )
 
-    def append(self, instruction: CircuitInstruction) -> CircuitInstruction:
+    def append(
+        self, instruction: CircuitInstruction, *, _standard_gate: bool = False
+    ) -> CircuitInstruction:
         if self._forbidden_message is not None:
             raise CircuitError(self._forbidden_message)
         if not self._allow_jumps:
diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py
index e339cb8d94bb..44155783d409 100644
--- a/qiskit/circuit/instruction.py
+++ b/qiskit/circuit/instruction.py
@@ -58,6 +58,7 @@ class Instruction(Operation):
     # Class attribute to treat like barrier for transpiler, unroller, drawer
     # NOTE: Using this attribute may change in the future (See issue # 5811)
     _directive = False
+    _standard_gate = None
 
     def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt", label=None):
         """Create a new instruction.
diff --git a/qiskit/circuit/instructionset.py b/qiskit/circuit/instructionset.py
index ac3d9fabd64b..576d5dee8267 100644
--- a/qiskit/circuit/instructionset.py
+++ b/qiskit/circuit/instructionset.py
@@ -140,13 +140,12 @@ def c_if(self, classical: Clbit | ClassicalRegister | int, val: int) -> "Instruc
             )
         if self._requester is not None:
             classical = self._requester(classical)
-        for instruction in self._instructions:
+        for idx, instruction in enumerate(self._instructions):
             if isinstance(instruction, CircuitInstruction):
                 updated = instruction.operation.c_if(classical, val)
-                if updated is not instruction.operation:
-                    raise CircuitError(
-                        "SingletonGate instances can only be added to InstructionSet via _add_ref"
-                    )
+                self._instructions[idx] = instruction.replace(
+                    operation=updated, condition=updated.condition
+                )
             else:
                 data, idx = instruction
                 instruction = data[idx]
diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py
index 2bbd5ca5650a..16cc0e3dbafa 100644
--- a/qiskit/circuit/library/blueprintcircuit.py
+++ b/qiskit/circuit/library/blueprintcircuit.py
@@ -17,7 +17,7 @@
 
 from qiskit._accelerate.circuit import CircuitData
 from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
-from qiskit.circuit.parametertable import ParameterTable, ParameterView
+from qiskit.circuit.parametertable import ParameterView
 
 
 class BlueprintCircuit(QuantumCircuit, ABC):
@@ -68,7 +68,6 @@ def _build(self) -> None:
     def _invalidate(self) -> None:
         """Invalidate the current circuit build."""
         self._data = CircuitData(self._data.qubits, self._data.clbits)
-        self._parameter_table = ParameterTable()
         self.global_phase = 0
         self._is_built = False
 
@@ -88,7 +87,6 @@ def qregs(self, qregs):
         self._ancillas = []
         self._qubit_indices = {}
         self._data = CircuitData(clbits=self._data.clbits)
-        self._parameter_table = ParameterTable()
         self.global_phase = 0
         self._is_built = False
 
@@ -122,10 +120,10 @@ def parameters(self) -> ParameterView:
             self._build()
         return super().parameters
 
-    def _append(self, instruction, _qargs=None, _cargs=None):
+    def _append(self, instruction, _qargs=None, _cargs=None, *, _standard_gate=False):
         if not self._is_built:
             self._build()
-        return super()._append(instruction, _qargs, _cargs)
+        return super()._append(instruction, _qargs, _cargs, _standard_gate=_standard_gate)
 
     def compose(
         self,
diff --git a/qiskit/circuit/library/standard_gates/ecr.py b/qiskit/circuit/library/standard_gates/ecr.py
index 73bb1bb03898..f00c02df538d 100644
--- a/qiskit/circuit/library/standard_gates/ecr.py
+++ b/qiskit/circuit/library/standard_gates/ecr.py
@@ -17,6 +17,7 @@
 from qiskit.circuit._utils import with_gate_array
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key
+from qiskit._accelerate.circuit import StandardGate
 from .rzx import RZXGate
 from .x import XGate
 
@@ -84,6 +85,8 @@ class ECRGate(SingletonGate):
                 \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.ECRGate
+
     def __init__(self, label=None, *, duration=None, unit="dt"):
         """Create new ECR gate."""
         super().__init__("ecr", 2, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py
index ccd758e47241..59d6b56373da 100644
--- a/qiskit/circuit/library/standard_gates/global_phase.py
+++ b/qiskit/circuit/library/standard_gates/global_phase.py
@@ -20,6 +20,7 @@
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.quantumcircuit import QuantumCircuit
 from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit._accelerate.circuit import StandardGate
 
 
 class GlobalPhaseGate(Gate):
@@ -36,6 +37,8 @@ class GlobalPhaseGate(Gate):
             \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.GlobalPhaseGate
+
     def __init__(
         self, phase: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
     ):
diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py
index cc06a071a3f6..2d273eed74d5 100644
--- a/qiskit/circuit/library/standard_gates/h.py
+++ b/qiskit/circuit/library/standard_gates/h.py
@@ -17,6 +17,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 _H_ARRAY = 1 / sqrt(2) * numpy.array([[1, 1], [1, -1]], dtype=numpy.complex128)
 
@@ -51,6 +52,8 @@ class HGate(SingletonGate):
             \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.HGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new H gate."""
         super().__init__("h", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/i.py b/qiskit/circuit/library/standard_gates/i.py
index 93523215d6f0..13a98ce0df8a 100644
--- a/qiskit/circuit/library/standard_gates/i.py
+++ b/qiskit/circuit/library/standard_gates/i.py
@@ -15,6 +15,7 @@
 from typing import Optional
 from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key
 from qiskit.circuit._utils import with_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 
 @with_gate_array([[1, 0], [0, 1]])
@@ -45,6 +46,8 @@ class IGate(SingletonGate):
              └───┘
     """
 
+    _standard_gate = StandardGate.IGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new Identity gate."""
         super().__init__("id", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py
index 6de0307dc798..1a792649feab 100644
--- a/qiskit/circuit/library/standard_gates/p.py
+++ b/qiskit/circuit/library/standard_gates/p.py
@@ -19,6 +19,7 @@
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit._accelerate.circuit import StandardGate
 
 
 class PhaseGate(Gate):
@@ -75,6 +76,8 @@ class PhaseGate(Gate):
         `1612.00858 `_
     """
 
+    _standard_gate = StandardGate.PhaseGate
+
     def __init__(
         self, theta: ParameterValueType, label: str | None = None, *, duration=None, unit="dt"
     ):
diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py
index eaa73cf87c91..5579f9d3707d 100644
--- a/qiskit/circuit/library/standard_gates/rx.py
+++ b/qiskit/circuit/library/standard_gates/rx.py
@@ -21,6 +21,7 @@
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit._accelerate.circuit import StandardGate
 
 
 class RXGate(Gate):
@@ -50,6 +51,8 @@ class RXGate(Gate):
             \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.RXGate
+
     def __init__(
         self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
     ):
diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py
index 633a518bca77..e27398cc2960 100644
--- a/qiskit/circuit/library/standard_gates/ry.py
+++ b/qiskit/circuit/library/standard_gates/ry.py
@@ -20,6 +20,7 @@
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit._accelerate.circuit import StandardGate
 
 
 class RYGate(Gate):
@@ -49,6 +50,8 @@ class RYGate(Gate):
             \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.RYGate
+
     def __init__(
         self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
     ):
diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py
index 3040f9568346..e8ee0f976036 100644
--- a/qiskit/circuit/library/standard_gates/rz.py
+++ b/qiskit/circuit/library/standard_gates/rz.py
@@ -17,6 +17,7 @@
 from qiskit.circuit.controlledgate import ControlledGate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit._accelerate.circuit import StandardGate
 
 
 class RZGate(Gate):
@@ -59,6 +60,8 @@ class RZGate(Gate):
         `1612.00858 `_
     """
 
+    _standard_gate = StandardGate.RZGate
+
     def __init__(
         self, phi: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
     ):
diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py
index 0e49783308c4..243a84701ef5 100644
--- a/qiskit/circuit/library/standard_gates/swap.py
+++ b/qiskit/circuit/library/standard_gates/swap.py
@@ -17,6 +17,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 
 _SWAP_ARRAY = numpy.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
@@ -58,6 +59,8 @@ class SwapGate(SingletonGate):
         |a, b\rangle \rightarrow |b, a\rangle
     """
 
+    _standard_gate = StandardGate.SwapGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new SWAP gate."""
         super().__init__("swap", 2, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py
index 0c003748a660..93ca85da0198 100644
--- a/qiskit/circuit/library/standard_gates/sx.py
+++ b/qiskit/circuit/library/standard_gates/sx.py
@@ -17,6 +17,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 
 _SX_ARRAY = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]]
@@ -62,6 +63,8 @@ class SXGate(SingletonGate):
 
     """
 
+    _standard_gate = StandardGate.SXGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new SX gate."""
         super().__init__("sx", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py
index 3d631898850a..3495bc180f08 100644
--- a/qiskit/circuit/library/standard_gates/u.py
+++ b/qiskit/circuit/library/standard_gates/u.py
@@ -21,6 +21,7 @@
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.parameterexpression import ParameterValueType
 from qiskit.circuit.quantumregister import QuantumRegister
+from qiskit._accelerate.circuit import StandardGate
 
 
 class UGate(Gate):
@@ -68,6 +69,8 @@ class UGate(Gate):
         U(\theta, 0, 0) = RY(\theta)
     """
 
+    _standard_gate = StandardGate.UGate
+
     def __init__(
         self,
         theta: ParameterValueType,
diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py
index 7195df90dc98..6e959b3e62cb 100644
--- a/qiskit/circuit/library/standard_gates/x.py
+++ b/qiskit/circuit/library/standard_gates/x.py
@@ -19,6 +19,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import _ctrl_state_to_int, with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 _X_ARRAY = [[0, 1], [1, 0]]
 
@@ -70,6 +71,8 @@ class XGate(SingletonGate):
         |1\rangle \rightarrow |0\rangle
     """
 
+    _standard_gate = StandardGate.XGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new X gate."""
         super().__init__("x", 1, [], label=label, duration=duration, unit=unit)
@@ -212,6 +215,8 @@ class CXGate(SingletonControlledGate):
         `|a, b\rangle \rightarrow |a, a \oplus b\rangle`
     """
 
+    _standard_gate = StandardGate.CXGate
+
     def __init__(
         self,
         label: Optional[str] = None,
@@ -362,6 +367,8 @@ class CCXGate(SingletonControlledGate):
 
     """
 
+    _standard_gate = StandardGate.CCXGate
+
     def __init__(
         self,
         label: Optional[str] = None,
diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py
index e69e1e2b794b..d62586aa2b9b 100644
--- a/qiskit/circuit/library/standard_gates/y.py
+++ b/qiskit/circuit/library/standard_gates/y.py
@@ -19,6 +19,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 _Y_ARRAY = [[0, -1j], [1j, 0]]
 
@@ -70,6 +71,8 @@ class YGate(SingletonGate):
         |1\rangle \rightarrow -i|0\rangle
     """
 
+    _standard_gate = StandardGate.YGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new Y gate."""
         super().__init__("y", 1, [], label=label, duration=duration, unit=unit)
@@ -197,6 +200,8 @@ class CYGate(SingletonControlledGate):
 
     """
 
+    _standard_gate = StandardGate.CYGate
+
     def __init__(
         self,
         label: Optional[str] = None,
diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py
index 2b69595936d8..19e4382cd846 100644
--- a/qiskit/circuit/library/standard_gates/z.py
+++ b/qiskit/circuit/library/standard_gates/z.py
@@ -20,6 +20,7 @@
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
+from qiskit._accelerate.circuit import StandardGate
 
 from .p import PhaseGate
 
@@ -73,6 +74,8 @@ class ZGate(SingletonGate):
         |1\rangle \rightarrow -|1\rangle
     """
 
+    _standard_gate = StandardGate.ZGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new Z gate."""
         super().__init__("z", 1, [], label=label, duration=duration, unit=unit)
@@ -181,6 +184,8 @@ class CZGate(SingletonControlledGate):
     the target qubit if the control qubit is in the :math:`|1\rangle` state.
     """
 
+    _standard_gate = StandardGate.CZGate
+
     def __init__(
         self,
         label: Optional[str] = None,
diff --git a/qiskit/circuit/parametertable.py b/qiskit/circuit/parametertable.py
index 6803126ec107..e5a41b1971c2 100644
--- a/qiskit/circuit/parametertable.py
+++ b/qiskit/circuit/parametertable.py
@@ -12,197 +12,8 @@
 """
 Look-up table for variable parameters in QuantumCircuit.
 """
-import operator
-import typing
-from collections.abc import MappingView, MutableMapping, MutableSet
 
-
-class ParameterReferences(MutableSet):
-    """A set of instruction parameter slot references.
-    Items are expected in the form ``(instruction, param_index)``. Membership
-    testing is overridden such that items that are otherwise value-wise equal
-    are still considered distinct if their ``instruction``\\ s are referentially
-    distinct.
-
-    In the case of the special value :attr:`.ParameterTable.GLOBAL_PHASE` for ``instruction``, the
-    ``param_index`` should be ``None``.
-    """
-
-    def _instance_key(self, ref):
-        return (id(ref[0]), ref[1])
-
-    def __init__(self, refs):
-        self._instance_ids = {}
-
-        for ref in refs:
-            if not isinstance(ref, tuple) or len(ref) != 2:
-                raise ValueError("refs must be in form (instruction, param_index)")
-            k = self._instance_key(ref)
-            self._instance_ids[k] = ref[0]
-
-    def __getstate__(self):
-        # Leave behind the reference IDs (keys of _instance_ids) since they'll
-        # be incorrect after unpickling on the other side.
-        return list(self)
-
-    def __setstate__(self, refs):
-        # Recompute reference IDs for the newly unpickled instructions.
-        self._instance_ids = {self._instance_key(ref): ref[0] for ref in refs}
-
-    def __len__(self):
-        return len(self._instance_ids)
-
-    def __iter__(self):
-        for (_, idx), instruction in self._instance_ids.items():
-            yield (instruction, idx)
-
-    def __contains__(self, x) -> bool:
-        return self._instance_key(x) in self._instance_ids
-
-    def __repr__(self) -> str:
-        return f"ParameterReferences({repr(list(self))})"
-
-    def add(self, value):
-        """Adds a reference to the listing if it's not already present."""
-        k = self._instance_key(value)
-        self._instance_ids[k] = value[0]
-
-    def discard(self, value):
-        k = self._instance_key(value)
-        self._instance_ids.pop(k, None)
-
-    def copy(self):
-        """Create a shallow copy."""
-        return ParameterReferences(self)
-
-
-class ParameterTable(MutableMapping):
-    """Class for tracking references to circuit parameters by specific
-    instruction instances.
-
-    Keys are parameters. Values are of type :class:`~ParameterReferences`,
-    which overrides membership testing to be referential for instructions,
-    and is set-like. Elements of :class:`~ParameterReferences`
-    are tuples of ``(instruction, param_index)``.
-    """
-
-    __slots__ = ["_table", "_keys", "_names"]
-
-    class _GlobalPhaseSentinel:
-        __slots__ = ()
-
-        def __copy__(self):
-            return self
-
-        def __deepcopy__(self, memo=None):
-            return self
-
-        def __reduce__(self):
-            return (operator.attrgetter("GLOBAL_PHASE"), (ParameterTable,))
-
-        def __repr__(self):
-            return ""
-
-    GLOBAL_PHASE = _GlobalPhaseSentinel()
-    """Tracking object to indicate that a reference refers to the global phase of a circuit."""
-
-    def __init__(self, mapping=None):
-        """Create a new instance, initialized with ``mapping`` if provided.
-
-        Args:
-            mapping (Mapping[Parameter, ParameterReferences]):
-                Mapping of parameter to the set of parameter slots that reference
-                it.
-
-        Raises:
-            ValueError: A value in ``mapping`` is not a :class:`~ParameterReferences`.
-        """
-        if mapping is not None:
-            if any(not isinstance(refs, ParameterReferences) for refs in mapping.values()):
-                raise ValueError("Values must be of type ParameterReferences")
-            self._table = mapping.copy()
-        else:
-            self._table = {}
-
-        self._keys = set(self._table)
-        self._names = {x.name: x for x in self._table}
-
-    def __getitem__(self, key):
-        return self._table[key]
-
-    def __setitem__(self, parameter, refs):
-        """Associate a parameter with the set of parameter slots ``(instruction, param_index)``
-        that reference it.
-
-        .. note::
-
-            Items in ``refs`` are considered unique if their ``instruction`` is referentially
-            unique. See :class:`~ParameterReferences` for details.
-
-        Args:
-            parameter (Parameter): the parameter
-            refs (Union[ParameterReferences, Iterable[(Instruction, int)]]): the parameter slots.
-                If this is an iterable, a new :class:`~ParameterReferences` is created from its
-                contents.
-        """
-        if not isinstance(refs, ParameterReferences):
-            refs = ParameterReferences(refs)
-
-        self._table[parameter] = refs
-        self._keys.add(parameter)
-        self._names[parameter.name] = parameter
-
-    def get_keys(self):
-        """Return a set of all keys in the parameter table
-
-        Returns:
-            set: A set of all the keys in the parameter table
-        """
-        return self._keys
-
-    def get_names(self):
-        """Return a set of all parameter names in the parameter table
-
-        Returns:
-            set: A set of all the names in the parameter table
-        """
-        return self._names.keys()
-
-    def parameter_from_name(self, name: str, default: typing.Any = None):
-        """Get a :class:`.Parameter` with references in this table by its string name.
-
-        If the parameter is not present, return the ``default`` value.
-
-        Args:
-            name: The name of the :class:`.Parameter`
-            default: The object that should be returned if the parameter is missing.
-        """
-        return self._names.get(name, default)
-
-    def discard_references(self, expression, key):
-        """Remove all references to parameters contained within ``expression`` at the given table
-        ``key``.  This also discards parameter entries from the table if they have no further
-        references.  No action is taken if the object is not tracked."""
-        for parameter in expression.parameters:
-            if (refs := self._table.get(parameter)) is not None:
-                if len(refs) == 1:
-                    del self[parameter]
-                else:
-                    refs.discard(key)
-
-    def __delitem__(self, key):
-        del self._table[key]
-        self._keys.discard(key)
-        del self._names[key.name]
-
-    def __iter__(self):
-        return iter(self._table)
-
-    def __len__(self):
-        return len(self._table)
-
-    def __repr__(self):
-        return f"ParameterTable({repr(self._table)})"
+from collections.abc import MappingView
 
 
 class ParameterView(MappingView):
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 606d0e04373a..238a2682522a 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -37,6 +37,7 @@
 )
 import numpy as np
 from qiskit._accelerate.circuit import CircuitData
+from qiskit._accelerate.circuit import StandardGate, PyGate, PyInstruction, PyOperation
 from qiskit.exceptions import QiskitError
 from qiskit.utils.multiprocessing import is_main_process
 from qiskit.circuit.instruction import Instruction
@@ -57,7 +58,7 @@
 from .parameterexpression import ParameterExpression, ParameterValueType
 from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
 from .classicalregister import ClassicalRegister, Clbit
-from .parametertable import ParameterReferences, ParameterTable, ParameterView
+from .parametertable import ParameterView
 from .parametervector import ParameterVector
 from .instructionset import InstructionSet
 from .operation import Operation
@@ -1124,14 +1125,10 @@ def __init__(
         self._calibrations: DefaultDict[str, dict[tuple, Any]] = defaultdict(dict)
         self.add_register(*regs)
 
-        # Parameter table tracks instructions with variable parameters.
-        self._parameter_table = ParameterTable()
-
         # Cache to avoid re-sorting parameters
         self._parameters = None
 
         self._layout = None
-        self._global_phase: ParameterValueType = 0
         self.global_phase = global_phase
 
         # Add classical variables.  Resolve inputs and captures first because they can't depend on
@@ -1159,6 +1156,15 @@ def __init__(
         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."""
 
+    @classmethod
+    def _from_circuit_data(cls, data: CircuitData) -> typing.Self:
+        """A private constructor from rust space circuit data."""
+        out = QuantumCircuit()
+        out.add_bits(data.qubits)
+        out.add_bits(data.clbits)
+        out._data = data
+        return out
+
     @staticmethod
     def from_instructions(
         instructions: Iterable[
@@ -1259,7 +1265,6 @@ def data(self, data_input: Iterable):
             data_input = list(data_input)
         self._data.clear()
         self._parameters = None
-        self._parameter_table = ParameterTable()
         # Repopulate the parameter table with any global-phase entries.
         self.global_phase = self.global_phase
         if not data_input:
@@ -1382,12 +1387,11 @@ def __deepcopy__(self, memo=None):
 
         # Avoids pulling self._data into a Python list
         # like we would when pickling.
-        result._data = self._data.copy()
+        result._data = self._data.copy(deepcopy=True)
         result._data.replace_bits(
             qubits=_copy.deepcopy(self._data.qubits, memo),
             clbits=_copy.deepcopy(self._data.clbits, memo),
         )
-        result._data.map_ops(lambda op: _copy.deepcopy(op, memo))
         return result
 
     @classmethod
@@ -1896,7 +1900,7 @@ def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var:
                 clbits = self.clbits[: other.num_clbits]
             if front:
                 # Need to keep a reference to the data for use after we've emptied it.
-                old_data = dest._data.copy()
+                old_data = dest._data.copy(copy_instructions=copy)
                 dest.clear()
                 dest.append(other, qubits, clbits, copy=copy)
                 for instruction in old_data:
@@ -2024,14 +2028,14 @@ def map_vars(op):
                     )
                 return n_op.copy() if n_op is op and copy else n_op
 
-            instructions = source._data.copy()
+            instructions = source._data.copy(copy_instructions=copy)
             instructions.replace_bits(qubits=new_qubits, clbits=new_clbits)
             instructions.map_ops(map_vars)
             dest._current_scope().extend(instructions)
 
         append_existing = None
         if front:
-            append_existing = dest._data.copy()
+            append_existing = dest._data.copy(copy_instructions=copy)
             dest.clear()
         copy_with_remapping(
             other,
@@ -2296,6 +2300,35 @@ def cbit_argument_conversion(self, clbit_representation: ClbitSpecifier) -> list
             clbit_representation, self.clbits, self._clbit_indices, Clbit
         )
 
+    def _append_standard_gate(
+        self,
+        op: StandardGate,
+        params: Sequence[ParameterValueType] | None = None,
+        qargs: Sequence[QubitSpecifier] | None = None,
+        cargs: Sequence[ClbitSpecifier] | None = None,
+        label: str | None = None,
+    ) -> InstructionSet:
+        """An internal method to bypass some checking when directly appending a standard gate."""
+        circuit_scope = self._current_scope()
+
+        if params is None:
+            params = []
+
+        expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []]
+        expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []]
+        if params is not None:
+            for param in params:
+                Gate.validate_parameter(op, param)
+
+        instructions = InstructionSet(resource_requester=circuit_scope.resolve_classical_resource)
+        broadcast_iter = Gate.broadcast_arguments(op, expanded_qargs, expanded_cargs)
+        for qarg, carg in broadcast_iter:
+            self._check_dups(qarg)
+            instruction = CircuitInstruction(op, qarg, carg, params=params, label=label)
+            circuit_scope.append(instruction, _standard_gate=True)
+            instructions._add_ref(circuit_scope.instructions, len(circuit_scope.instructions) - 1)
+        return instructions
+
     def append(
         self,
         instruction: Operation | CircuitInstruction,
@@ -2393,16 +2426,47 @@ def append(
             if isinstance(operation, Instruction)
             else Instruction.broadcast_arguments(operation, expanded_qargs, expanded_cargs)
         )
+        params = None
+        if isinstance(operation, Gate):
+            params = operation.params
+            operation = PyGate(
+                operation.name,
+                operation.num_qubits,
+                operation.num_clbits,
+                len(params),
+                operation,
+            )
+        elif isinstance(operation, Instruction):
+            params = operation.params
+            operation = PyInstruction(
+                operation.name,
+                operation.num_qubits,
+                operation.num_clbits,
+                len(params),
+                operation,
+            )
+        elif isinstance(operation, Operation):
+            params = getattr(operation, "params", ())
+            operation = PyOperation(
+                operation.name,
+                operation.num_qubits,
+                operation.num_clbits,
+                len(params),
+                operation,
+            )
+
         for qarg, carg in broadcast_iter:
             self._check_dups(qarg)
-            instruction = CircuitInstruction(operation, qarg, carg)
+            instruction = CircuitInstruction(operation, qarg, carg, params=params)
             circuit_scope.append(instruction)
             instructions._add_ref(circuit_scope.instructions, len(circuit_scope.instructions) - 1)
         return instructions
 
     # Preferred new style.
     @typing.overload
-    def _append(self, instruction: CircuitInstruction) -> CircuitInstruction: ...
+    def _append(
+        self, instruction: CircuitInstruction, *, _standard_gate: bool
+    ) -> CircuitInstruction: ...
 
     # To-be-deprecated old style.
     @typing.overload
@@ -2413,7 +2477,7 @@ def _append(
         cargs: Sequence[Clbit],
     ) -> Operation: ...
 
-    def _append(self, instruction, qargs=(), cargs=()):
+    def _append(self, instruction, qargs=(), cargs=(), *, _standard_gate: bool = False):
         """Append an instruction to the end of the circuit, modifying the circuit in place.
 
         .. warning::
@@ -2454,40 +2518,39 @@ def _append(self, instruction, qargs=(), cargs=()):
 
         :meta public:
         """
+        if _standard_gate:
+            new_param = self._data.append(instruction)
+            if new_param:
+                self._parameters = None
+            self.duration = None
+            self.unit = "dt"
+            return instruction
+
         old_style = not isinstance(instruction, CircuitInstruction)
         if old_style:
             instruction = CircuitInstruction(instruction, qargs, cargs)
-        self._data.append(instruction)
-        self._track_operation(instruction.operation)
-        return instruction.operation if old_style else instruction
+        # If there is a reference to the outer circuit in an
+        # instruction param the inner rust append method will raise a runtime error.
+        # When this happens we need to handle the parameters separately.
+        # This shouldn't happen in practice but 2 tests were doing this and it's not
+        # explicitly prohibted by the API so this and the `params` optional argument
+        # path guard against it.
+        try:
+            new_param = self._data.append(instruction)
+        except RuntimeError:
+            params = []
+            for idx, param in enumerate(instruction.operation.params):
+                if isinstance(param, (ParameterExpression, QuantumCircuit)):
+                    params.append((idx, list(set(param.parameters))))
+            new_param = self._data.append(instruction, params)
+        if new_param:
+            # clear cache if new parameter is added
+            self._parameters = None
 
-    def _track_operation(self, operation: Operation):
-        """Sync all non-data-list internal data structures for a newly tracked operation."""
-        if isinstance(operation, Instruction):
-            self._update_parameter_table(operation)
+        # Invalidate whole circuit duration if an instruction is added
         self.duration = None
         self.unit = "dt"
-
-    def _update_parameter_table(self, instruction: Instruction):
-        for param_index, param in enumerate(instruction.params):
-            if isinstance(param, (ParameterExpression, QuantumCircuit)):
-                # Scoped constructs like the control-flow ops use QuantumCircuit as a parameter.
-                atomic_parameters = set(param.parameters)
-            else:
-                atomic_parameters = set()
-
-            for parameter in atomic_parameters:
-                if parameter in self._parameter_table:
-                    self._parameter_table[parameter].add((instruction, param_index))
-                else:
-                    if parameter.name in self._parameter_table.get_names():
-                        raise CircuitError(f"Name conflict on adding parameter: {parameter.name}")
-                    self._parameter_table[parameter] = ParameterReferences(
-                        ((instruction, param_index),)
-                    )
-
-                    # clear cache if new parameter is added
-                    self._parameters = None
+        return instruction.operation if old_style else instruction
 
     @typing.overload
     def get_parameter(self, name: str, default: T) -> Union[Parameter, T]: ...
@@ -2538,7 +2601,7 @@ def get_parameter(self, name: str, default: typing.Any = ...) -> Parameter:
                 A similar method, but for :class:`.expr.Var` run-time variables instead of
                 :class:`.Parameter` compile-time parameters.
         """
-        if (parameter := self._parameter_table.parameter_from_name(name, None)) is None:
+        if (parameter := self._data.get_param_from_name(name)) is None:
             if default is Ellipsis:
                 raise KeyError(f"no parameter named '{name}' is present")
             return default
@@ -3415,13 +3478,7 @@ def num_nonlocal_gates(self) -> int:
 
         Conditional nonlocal gates are also included.
         """
-        multi_qubit_gates = 0
-        for instruction in self._data:
-            if instruction.operation.num_qubits > 1 and not getattr(
-                instruction.operation, "_directive", False
-            ):
-                multi_qubit_gates += 1
-        return multi_qubit_gates
+        return self._data.num_nonlocal_gates()
 
     def get_instructions(self, name: str) -> list[CircuitInstruction]:
         """Get instructions matching name.
@@ -3535,29 +3592,6 @@ def copy(self, name: str | None = None) -> typing.Self:
         """
         cpy = self.copy_empty_like(name)
         cpy._data = self._data.copy()
-
-        # The special global-phase sentinel doesn't need copying, but it's
-        # added here to ensure it's recognised. The global phase itself was
-        # already copied over in `copy_empty_like`.
-        operation_copies = {id(ParameterTable.GLOBAL_PHASE): ParameterTable.GLOBAL_PHASE}
-
-        def memo_copy(op):
-            if (out := operation_copies.get(id(op))) is not None:
-                return out
-            copied = op.copy()
-            operation_copies[id(op)] = copied
-            return copied
-
-        cpy._data.map_ops(memo_copy)
-        cpy._parameter_table = ParameterTable(
-            {
-                param: ParameterReferences(
-                    (operation_copies[id(operation)], param_index)
-                    for operation, param_index in self._parameter_table[param]
-                )
-                for param in self._parameter_table
-            }
-        )
         return cpy
 
     def copy_empty_like(
@@ -3636,12 +3670,9 @@ def copy_empty_like(
         else:  # pragma: no cover
             raise ValueError(f"unknown vars_mode: '{vars_mode}'")
 
-        cpy._parameter_table = ParameterTable()
-        for parameter in getattr(cpy.global_phase, "parameters", ()):
-            cpy._parameter_table[parameter] = ParameterReferences(
-                [(ParameterTable.GLOBAL_PHASE, None)]
-            )
-        cpy._data = CircuitData(self._data.qubits, self._data.clbits)
+        cpy._data = CircuitData(
+            self._data.qubits, self._data.clbits, global_phase=self._data.global_phase
+        )
 
         cpy._calibrations = _copy.deepcopy(self._calibrations)
         cpy._metadata = _copy.deepcopy(self._metadata)
@@ -3661,7 +3692,6 @@ def clear(self) -> None:
                 quantum and classical typed data, but without mutating the original circuit.
         """
         self._data.clear()
-        self._parameter_table.clear()
         # Repopulate the parameter table with any phase symbols.
         self.global_phase = self.global_phase
 
@@ -3945,10 +3975,9 @@ def remove_final_measurements(self, inplace: bool = True) -> Optional["QuantumCi
         circ._clbit_indices = {}
 
         # Clear instruction info
-        circ._data = CircuitData(qubits=circ._data.qubits, reserve=len(circ._data))
-        circ._parameter_table.clear()
-        # Repopulate the parameter table with any global-phase entries.
-        circ.global_phase = circ.global_phase
+        circ._data = CircuitData(
+            qubits=circ._data.qubits, reserve=len(circ._data), global_phase=circ.global_phase
+        )
 
         # We must add the clbits first to preserve the original circuit
         # order. This way, add_register never adds clbits and just
@@ -4021,7 +4050,7 @@ def global_phase(self) -> ParameterValueType:
         """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
+        return self._data.global_phase
 
     @global_phase.setter
     def global_phase(self, angle: ParameterValueType):
@@ -4032,23 +4061,18 @@ def global_phase(self, angle: ParameterValueType):
         """
         # If we're currently parametric, we need to throw away the references.  This setter is
         # called by some subclasses before the inner `_global_phase` is initialised.
-        global_phase_reference = (ParameterTable.GLOBAL_PHASE, None)
-        if isinstance(previous := getattr(self, "_global_phase", None), ParameterExpression):
+        if isinstance(getattr(self._data, "global_phase", None), ParameterExpression):
             self._parameters = None
-            self._parameter_table.discard_references(previous, global_phase_reference)
-
-        if isinstance(angle, ParameterExpression) and angle.parameters:
-            for parameter in angle.parameters:
-                if parameter not in self._parameter_table:
-                    self._parameters = None
-                    self._parameter_table[parameter] = ParameterReferences(())
-                self._parameter_table[parameter].add(global_phase_reference)
+        if isinstance(angle, ParameterExpression):
+            if angle.parameters:
+                self._parameters = None
         else:
             angle = _normalize_global_phase(angle)
+
         if self._control_flow_scopes:
             self._control_flow_scopes[-1].global_phase = angle
         else:
-            self._global_phase = angle
+            self._data.global_phase = angle
 
     @property
     def parameters(self) -> ParameterView:
@@ -4118,7 +4142,7 @@ def parameters(self) -> ParameterView:
     @property
     def num_parameters(self) -> int:
         """The number of parameter objects in the circuit."""
-        return len(self._parameter_table)
+        return self._data.num_params()
 
     def _unsorted_parameters(self) -> set[Parameter]:
         """Efficiently get all parameters in the circuit, without any sorting overhead.
@@ -4131,7 +4155,7 @@ def _unsorted_parameters(self) -> set[Parameter]:
         """
         # This should be free, by accessing the actual backing data structure of the table, but that
         # means that we need to copy it if adding keys from the global phase.
-        return self._parameter_table.get_keys()
+        return self._data.get_params_unsorted()
 
     @overload
     def assign_parameters(
@@ -4280,7 +4304,7 @@ def assign_parameters(  # pylint: disable=missing-raises-doc
         target._parameters = None
         # This is deliberately eager, because we want the side effect of clearing the table.
         all_references = [
-            (parameter, value, target._parameter_table.pop(parameter, ()))
+            (parameter, value, target._data.pop_param(parameter.uuid.int, parameter.name, ()))
             for parameter, value in parameter_binds.items()
         ]
         seen_operations = {}
@@ -4291,20 +4315,28 @@ def assign_parameters(  # pylint: disable=missing-raises-doc
                 if isinstance(bound_value, ParameterExpression)
                 else ()
             )
-            for operation, index in references:
-                seen_operations[id(operation)] = operation
-                if operation is ParameterTable.GLOBAL_PHASE:
+            for inst_index, index in references:
+                if inst_index == self._data.global_phase_param_index:
+                    operation = None
+                    seen_operations[inst_index] = None
                     assignee = target.global_phase
                     validate = _normalize_global_phase
                 else:
+                    operation = target._data[inst_index].operation
+                    seen_operations[inst_index] = operation
                     assignee = operation.params[index]
                     validate = operation.validate_parameter
                 if isinstance(assignee, ParameterExpression):
                     new_parameter = assignee.assign(to_bind, bound_value)
                     for parameter in update_parameters:
-                        if parameter not in target._parameter_table:
-                            target._parameter_table[parameter] = ParameterReferences(())
-                        target._parameter_table[parameter].add((operation, index))
+                        if not target._data.contains_param(parameter.uuid.int):
+                            target._data.add_new_parameter(parameter, inst_index, index)
+                        else:
+                            target._data.update_parameter_entry(
+                                parameter.uuid.int,
+                                inst_index,
+                                index,
+                            )
                     if not new_parameter.parameters:
                         new_parameter = validate(new_parameter.numeric())
                 elif isinstance(assignee, QuantumCircuit):
@@ -4316,12 +4348,18 @@ def assign_parameters(  # pylint: disable=missing-raises-doc
                         f"Saw an unknown type during symbolic binding: {assignee}."
                         " This may indicate an internal logic error in symbol tracking."
                     )
-                if operation is ParameterTable.GLOBAL_PHASE:
+                if inst_index == self._data.global_phase_param_index:
                     # We've already handled parameter table updates in bulk, so we need to skip the
                     # public setter trying to do it again.
-                    target._global_phase = new_parameter
+                    target._data.global_phase = new_parameter
                 else:
-                    operation.params[index] = new_parameter
+                    temp_params = operation.params
+                    temp_params[index] = new_parameter
+                    operation.params = temp_params
+                    target._data.setitem_no_param_table_update(
+                        inst_index,
+                        target._data[inst_index].replace(operation=operation, params=temp_params),
+                    )
 
         # After we've been through everything at the top level, make a single visit to each
         # operation we've seen, rebinding its definition if necessary.
@@ -4368,6 +4406,7 @@ def map_calibration(qubits, parameters, schedule):
                 for gate, calibrations in target._calibrations.items()
             ),
         )
+        target._parameters = None
         return None if inplace else target
 
     def _unroll_param_dict(
@@ -4450,9 +4489,7 @@ def h(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.h import HGate
-
-        return self.append(HGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.HGate, [], qargs=[qubit])
 
     def ch(
         self,
@@ -4496,9 +4533,7 @@ def id(self, qubit: QubitSpecifier) -> InstructionSet:  # pylint: disable=invali
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.i import IGate
-
-        return self.append(IGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.IGate, None, qargs=[qubit])
 
     def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.MSGate`.
@@ -4529,9 +4564,7 @@ def p(self, theta: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.p import PhaseGate
-
-        return self.append(PhaseGate(theta), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.PhaseGate, [theta], qargs=[qubit])
 
     def cp(
         self,
@@ -4712,9 +4745,7 @@ def rx(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.rx import RXGate
-
-        return self.append(RXGate(theta, label=label), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], None, label=label)
 
     def crx(
         self,
@@ -4783,9 +4814,7 @@ def ry(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.ry import RYGate
-
-        return self.append(RYGate(theta, label=label), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], None, label=label)
 
     def cry(
         self,
@@ -4851,9 +4880,7 @@ def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.rz import RZGate
-
-        return self.append(RZGate(phi), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit], None)
 
     def crz(
         self,
@@ -4937,9 +4964,9 @@ def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.ecr import ECRGate
-
-        return self.append(ECRGate(), [qubit1, qubit2], [], copy=False)
+        return self._append_standard_gate(
+            StandardGate.ECRGate, [], qargs=[qubit1, qubit2], cargs=None
+        )
 
     def s(self, qubit: QubitSpecifier) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.SGate`.
@@ -5044,9 +5071,12 @@ def swap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.swap import SwapGate
-
-        return self.append(SwapGate(), [qubit1, qubit2], [], copy=False)
+        return self._append_standard_gate(
+            StandardGate.SwapGate,
+            [],
+            qargs=[qubit1, qubit2],
+            cargs=None,
+        )
 
     def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.iSwapGate`.
@@ -5107,9 +5137,7 @@ def sx(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.sx import SXGate
-
-        return self.append(SXGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.SXGate, None, qargs=[qubit])
 
     def sxdg(self, qubit: QubitSpecifier) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.SXdgGate`.
@@ -5207,9 +5235,7 @@ def u(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.u import UGate
-
-        return self.append(UGate(theta, phi, lam), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.UGate, [theta, phi, lam], qargs=[qubit])
 
     def cu(
         self,
@@ -5262,9 +5288,7 @@ def x(self, qubit: QubitSpecifier, label: str | None = None) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.x import XGate
-
-        return self.append(XGate(label=label), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.XGate, None, qargs=[qubit], label=label)
 
     def cx(
         self,
@@ -5288,14 +5312,17 @@ def cx(
         Returns:
             A handle to the instructions created.
         """
+        if ctrl_state is not None:
+            from .library.standard_gates.x import CXGate
 
-        from .library.standard_gates.x import CXGate
-
-        return self.append(
-            CXGate(label=label, ctrl_state=ctrl_state),
-            [control_qubit, target_qubit],
-            [],
-            copy=False,
+            return self.append(
+                CXGate(label=label, ctrl_state=ctrl_state),
+                [control_qubit, target_qubit],
+                [],
+                copy=False,
+            )
+        return self._append_standard_gate(
+            StandardGate.CXGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
         )
 
     def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet:
@@ -5336,13 +5363,20 @@ def ccx(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.x import CCXGate
+        if ctrl_state is not None:
+            from .library.standard_gates.x import CCXGate
 
-        return self.append(
-            CCXGate(ctrl_state=ctrl_state),
-            [control_qubit1, control_qubit2, target_qubit],
+            return self.append(
+                CCXGate(ctrl_state=ctrl_state),
+                [control_qubit1, control_qubit2, target_qubit],
+                [],
+                copy=False,
+            )
+        return self._append_standard_gate(
+            StandardGate.CCXGate,
             [],
-            copy=False,
+            qargs=[control_qubit1, control_qubit2, target_qubit],
+            cargs=None,
         )
 
     def mcx(
@@ -5440,9 +5474,7 @@ def y(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.y import YGate
-
-        return self.append(YGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.YGate, None, qargs=[qubit])
 
     def cy(
         self,
@@ -5466,13 +5498,18 @@ def cy(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.y import CYGate
+        if ctrl_state is not None:
+            from .library.standard_gates.y import CYGate
 
-        return self.append(
-            CYGate(label=label, ctrl_state=ctrl_state),
-            [control_qubit, target_qubit],
-            [],
-            copy=False,
+            return self.append(
+                CYGate(label=label, ctrl_state=ctrl_state),
+                [control_qubit, target_qubit],
+                [],
+                copy=False,
+            )
+
+        return self._append_standard_gate(
+            StandardGate.CYGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
         )
 
     def z(self, qubit: QubitSpecifier) -> InstructionSet:
@@ -5486,9 +5523,7 @@ def z(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.z import ZGate
-
-        return self.append(ZGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.ZGate, None, qargs=[qubit])
 
     def cz(
         self,
@@ -5512,13 +5547,18 @@ def cz(
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.z import CZGate
+        if ctrl_state is not None:
+            from .library.standard_gates.z import CZGate
 
-        return self.append(
-            CZGate(label=label, ctrl_state=ctrl_state),
-            [control_qubit, target_qubit],
-            [],
-            copy=False,
+            return self.append(
+                CZGate(label=label, ctrl_state=ctrl_state),
+                [control_qubit, target_qubit],
+                [],
+                copy=False,
+            )
+
+        return self._append_standard_gate(
+            StandardGate.CZGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
         )
 
     def ccz(
@@ -5907,36 +5947,9 @@ def _pop_previous_instruction_in_scope(self) -> CircuitInstruction:
         if not self._data:
             raise CircuitError("This circuit contains no instructions.")
         instruction = self._data.pop()
-        if isinstance(instruction.operation, Instruction):
-            self._update_parameter_table_on_instruction_removal(instruction)
+        self._parameters = None
         return instruction
 
-    def _update_parameter_table_on_instruction_removal(self, instruction: CircuitInstruction):
-        """Update the :obj:`.ParameterTable` of this circuit given that an instance of the given
-        ``instruction`` has just been removed from the circuit.
-
-        .. note::
-
-            This does not account for the possibility for the same instruction instance being added
-            more than once to the circuit.  At the time of writing (2021-11-17, main commit 271a82f)
-            there is a defensive ``deepcopy`` of parameterised instructions inside
-            :meth:`.QuantumCircuit.append`, so this should be safe.  Trying to account for it would
-            involve adding a potentially quadratic-scaling loop to check each entry in ``data``.
-        """
-        atomic_parameters: list[tuple[Parameter, int]] = []
-        for index, parameter in enumerate(instruction.operation.params):
-            if isinstance(parameter, (ParameterExpression, QuantumCircuit)):
-                atomic_parameters.extend((p, index) for p in parameter.parameters)
-        for atomic_parameter, index in atomic_parameters:
-            new_entries = self._parameter_table[atomic_parameter].copy()
-            new_entries.discard((instruction.operation, index))
-            if not new_entries:
-                del self._parameter_table[atomic_parameter]
-                # Invalidate cache.
-                self._parameters = None
-            else:
-                self._parameter_table[atomic_parameter] = new_entries
-
     @typing.overload
     def while_loop(
         self,
@@ -6582,13 +6595,15 @@ def __init__(self, circuit: QuantumCircuit):
     def instructions(self):
         return self.circuit._data
 
-    def append(self, instruction):
+    def append(self, instruction, *, _standard_gate: bool = False):
         # QuantumCircuit._append is semi-public, so we just call back to it.
-        return self.circuit._append(instruction)
+        return self.circuit._append(instruction, _standard_gate=_standard_gate)
 
     def extend(self, data: CircuitData):
         self.circuit._data.extend(data)
-        data.foreach_op(self.circuit._track_operation)
+        self.circuit._parameters = None
+        self.circuit.duration = None
+        self.circuit.unit = "dt"
 
     def resolve_classical_resource(self, specifier):
         # This is slightly different to cbit_argument_conversion, because it should not
diff --git a/qiskit/circuit/quantumcircuitdata.py b/qiskit/circuit/quantumcircuitdata.py
index 3e29f36c6bee..9ecc8e6a6cac 100644
--- a/qiskit/circuit/quantumcircuitdata.py
+++ b/qiskit/circuit/quantumcircuitdata.py
@@ -45,8 +45,6 @@ def __setitem__(self, key, value):
             operation, qargs, cargs = value
         value = self._resolve_legacy_value(operation, qargs, cargs)
         self._circuit._data[key] = value
-        if isinstance(value.operation, Instruction):
-            self._circuit._update_parameter_table(value.operation)
 
     def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction:
         """Resolve the old-style 3-tuple into the new :class:`CircuitInstruction` type."""
@@ -76,7 +74,7 @@ def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction:
         return CircuitInstruction(operation, tuple(qargs), tuple(cargs))
 
     def insert(self, index, value):
-        self._circuit._data.insert(index, CircuitInstruction(None, (), ()))
+        self._circuit._data.insert(index, value.replace(qubits=(), clbits=()))
         try:
             self[index] = value
         except CircuitError:
diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py
index 2bdcbfef3583..1a5907c3ec84 100644
--- a/qiskit/converters/circuit_to_instruction.py
+++ b/qiskit/converters/circuit_to_instruction.py
@@ -11,7 +11,6 @@
 # that they have been altered from the originals.
 
 """Helper function for converting a circuit to an instruction."""
-from qiskit.circuit.parametertable import ParameterTable, ParameterReferences
 from qiskit.exceptions import QiskitError
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.quantumregister import QuantumRegister
@@ -121,7 +120,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None
         regs.append(creg)
 
     clbit_map = {bit: creg[idx] for idx, bit in enumerate(circuit.clbits)}
-    operation_map = {id(ParameterTable.GLOBAL_PHASE): ParameterTable.GLOBAL_PHASE}
+    operation_map = {}
 
     def fix_condition(op):
         original_id = id(op)
@@ -149,15 +148,6 @@ def fix_condition(op):
 
     qc = QuantumCircuit(*regs, name=out_instruction.name)
     qc._data = data
-    qc._parameter_table = ParameterTable(
-        {
-            param: ParameterReferences(
-                (operation_map[id(operation)], param_index)
-                for operation, param_index in target._parameter_table[param]
-            )
-            for param in target._parameter_table
-        }
-    )
 
     if circuit.global_phase:
         qc.global_phase = circuit.global_phase
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index 16bc2529ca74..7f737af76eb4 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -252,6 +252,9 @@ def __init__(self, includelist, basis_gates=()):
     def __setitem__(self, name_str, instruction):
         self._data[name_str] = instruction.base_class
         self._data[id(instruction)] = name_str
+        ctrl_state = str(getattr(instruction, "ctrl_state", ""))
+
+        self._data[f"{instruction.name}_{ctrl_state}_{instruction.params}"] = name_str
 
     def __getitem__(self, key):
         if isinstance(key, Instruction):
@@ -262,7 +265,9 @@ def __getitem__(self, key):
                 pass
             # Built-in gates.
             if key.name not in self._data:
-                raise KeyError(key)
+                # Registerd qiskit standard gate without stgates.inc
+                ctrl_state = str(getattr(key, "ctrl_state", ""))
+                return self._data[f"{key.name}_{ctrl_state}_{key.params}"]
             return key.name
         return self._data[key]
 
@@ -1102,7 +1107,8 @@ def is_loop_variable(circuit, parameter):
         # _should_ be an intrinsic part of the parameter, or somewhere publicly accessible, but
         # Terra doesn't have those concepts yet.  We can only try and guess at the type by looking
         # at all the places it's used in the circuit.
-        for instruction, index in circuit._parameter_table[parameter]:
+        for instr_index, index in circuit._data._get_param(parameter.uuid.int):
+            instruction = circuit.data[instr_index].operation
             if isinstance(instruction, ForLoopOp):
                 # The parameters of ForLoopOp are (indexset, loop_parameter, body).
                 if index == 1:
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py
index 4f49879063ec..75b455410f49 100644
--- a/qiskit/quantum_info/operators/dihedral/dihedral.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral.py
@@ -452,8 +452,7 @@ def conjugate(self):
             new_qubits = [bit_indices[tup] for tup in instruction.qubits]
             if instruction.operation.name == "p":
                 params = 2 * np.pi - instruction.operation.params[0]
-                instruction.operation.params[0] = params
-                new_circ.append(instruction.operation, new_qubits)
+                new_circ.p(params, new_qubits)
             elif instruction.operation.name == "t":
                 instruction.operation.name = "tdg"
                 new_circ.append(instruction.operation, new_qubits)
diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
index 7cb309dd9aa1..806e001f2bda 100644
--- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
+++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py
@@ -361,17 +361,17 @@ def _pad(
             theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv)
             if isinstance(next_node, DAGOpNode) and isinstance(next_node.op, (UGate, U3Gate)):
                 # Absorb the inverse into the successor (from left in circuit)
-                theta_r, phi_r, lam_r = next_node.op.params
-                next_node.op.params = Optimize1qGates.compose_u3(
-                    theta_r, phi_r, lam_r, theta, phi, lam
-                )
+                op = next_node.op
+                theta_r, phi_r, lam_r = op.params
+                op.params = Optimize1qGates.compose_u3(theta_r, phi_r, lam_r, theta, phi, lam)
+                next_node.op = op
                 sequence_gphase += phase
             elif isinstance(prev_node, DAGOpNode) and isinstance(prev_node.op, (UGate, U3Gate)):
                 # Absorb the inverse into the predecessor (from right in circuit)
-                theta_l, phi_l, lam_l = prev_node.op.params
-                prev_node.op.params = Optimize1qGates.compose_u3(
-                    theta, phi, lam, theta_l, phi_l, lam_l
-                )
+                op = prev_node.op
+                theta_l, phi_l, lam_l = op.params
+                op.params = Optimize1qGates.compose_u3(theta, phi, lam, theta_l, phi_l, lam_l)
+                prev_node.op = op
                 sequence_gphase += phase
             else:
                 # Don't do anything if there's no single-qubit gate to absorb the inverse
diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py
index 3792a149fd71..69bea32acca7 100644
--- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py
+++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py
@@ -70,8 +70,9 @@ def _get_node_duration(
             duration = dag.calibrations[node.op.name][cal_key].duration
 
             # Note that node duration is updated (but this is analysis pass)
-            node.op = node.op.to_mutable()
-            node.op.duration = duration
+            op = node.op.to_mutable()
+            op.duration = duration
+            node.op = op
         else:
             duration = node.op.duration
 
diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py
index 25672c137f34..08ac932d8aeb 100644
--- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py
+++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py
@@ -105,9 +105,10 @@ def run(self, dag: DAGCircuit):
                 )
             except TranspilerError:
                 continue
-            node.op = node.op.to_mutable()
-            node.op.duration = duration
-            node.op.unit = time_unit
+            op = node.op.to_mutable()
+            op.duration = duration
+            op.unit = time_unit
+            node.op = op
 
         self.property_set["time_unit"] = time_unit
         return dag
diff --git a/releasenotes/notes/circuit-gates-rust-5c6ab6c58f7fd2c9.yaml b/releasenotes/notes/circuit-gates-rust-5c6ab6c58f7fd2c9.yaml
new file mode 100644
index 000000000000..d826bc15e488
--- /dev/null
+++ b/releasenotes/notes/circuit-gates-rust-5c6ab6c58f7fd2c9.yaml
@@ -0,0 +1,79 @@
+---
+features_circuits:
+  - |
+    A native rust representation of Qiskit's standard gate library has been added. When a standard gate
+    is added to a :class:`~.QuantumCircuit` or :class:`~.DAGCircuit` it is now represented in a more
+    efficient manner directly in Rust seamlessly. Accessing that gate object from a circuit or dag will
+    return a new Python object representing the standard gate. This leads to faster and more efficient
+    transpilation and manipulation of circuits for functionality written in Rust.
+features_misc:
+  - |
+    Added a new build-time environment variable ``QISKIT_NO_CACHE_GATES`` which
+    when set to a value of ``1`` (i.e. ``QISKIT_NO_CACHE_GATES=1``) which
+    decreases the memory overhead of a :class:`.CircuitInstruction` and
+    :class:`.DAGOpNode` object at the cost of decreased runtime on multiple
+    accesses to :attr:`.CircuitInstruction.operation` and :attr:`.DAGOpNode.op`.
+    If this environment variable is set when building the Qiskit python package
+    from source the caching of the return of these attributes will be disabled.
+upgrade_circuits:
+  - |
+    The :class:`.Operation` instances of :attr:`.DAGOpNode.op`
+    being returned will not necessarily share a common reference to the
+    underlying object anymore. This was never guaranteed to be the case and
+    mutating the :attr:`~.DAGOpNode.op` directly by reference
+    was unsound and always likely to corrupt the dag's internal state tracking
+    Due to the internal refactor of the :class:`.QuantumCircuit` and
+    :class:`.DAGCircuit` to store standard gates in rust the output object from
+    :attr:`.DAGOpNode.op` will now likely be a copy instead of a shared instance. If you
+    need to mutate an element should ensure that you either do::
+
+      op = dag_node.op
+      op.params[0] = 3.14159
+      dag_node.op = op
+
+    or::
+
+      op = dag_node.op
+      op.params[0] = 3.14159
+      dag.substitute_node(dag_node, op)
+
+    instead of doing something like::
+
+      dag_node.op.params[0] = 3.14159
+
+    which will not work for any standard gates in this release. It would have
+    likely worked by chance in a previous release but was never an API guarantee.
+  - |
+    The :class:`.Operation` instances of :attr:`.CircuitInstruction.operation`
+    being returned will not necessarily share a common reference to the
+    underlying object anymore. This was never guaranteed to be the case and
+    mutating the :attr:`~.CircuitInstruction.operation` directly by reference
+    was unsound and always likely to corrupt the circuit, especially when
+    parameters were in use. Due to the internal refactor of the QuantumCircuit
+    to store standard gates in rust the output object from
+    :attr:`.CircuitInstruction.operation` will now likely be a copy instead
+    of a shared instance. If you need to mutate an element in the circuit (which
+    is strongly **not** recommended as it's inefficient and error prone) you
+    should ensure that you do::
+
+      from qiskit.circuit import QuantumCircuit
+
+      qc = QuantumCircuit(1)
+      qc.p(0)
+
+      op = qc.data[0].operation
+      op.params[0] = 3.14
+
+      qc.data[0] = qc.data[0].replace(operation=op)
+
+    instead of doing something like::
+
+      from qiskit.circuit import QuantumCircuit
+
+      qc = QuantumCircuit(1)
+      qc.p(0)
+
+      qc.data[0].operation.params[0] = 3.14
+
+    which will not work for any standard gates in this release. It would have
+    likely worked by chance in a previous release but was never an API guarantee.
diff --git a/requirements-optional.txt b/requirements-optional.txt
index 36985cdd7cd9..3dfc2031d026 100644
--- a/requirements-optional.txt
+++ b/requirements-optional.txt
@@ -19,7 +19,7 @@ seaborn>=0.9.0
 
 # Functionality and accelerators.
 qiskit-aer
-qiskit-qasm3-import
+qiskit-qasm3-import>=0.5.0
 python-constraint>=1.4
 cvxpy
 scikit-learn>=0.20.0
diff --git a/setup.py b/setup.py
index 9bb5b04ae6ec..38af5286e817 100644
--- a/setup.py
+++ b/setup.py
@@ -30,6 +30,17 @@
 # it's an editable installation.
 rust_debug = True if os.getenv("RUST_DEBUG") == "1" else None
 
+# If QISKIT_NO_CACHE_GATES is set then don't enable any features while building
+#
+# TODO: before final release we should reverse this by default once the default transpiler pass
+# is all in rust (default to no caching and make caching an opt-in feature). This is opt-out
+# right now to avoid the runtime overhead until we are leveraging the rust gates infrastructure.
+if os.getenv("QISKIT_NO_CACHE_GATES") == "1":
+    features = []
+else:
+    features = ["cache_pygates"]
+
+
 setup(
     rust_extensions=[
         RustExtension(
@@ -37,6 +48,7 @@
             "crates/pyext/Cargo.toml",
             binding=Binding.PyO3,
             debug=rust_debug,
+            features=features,
         )
     ],
     options={"bdist_wheel": {"py_limited_api": "cp38"}},
diff --git a/test/python/circuit/library/test_blueprintcircuit.py b/test/python/circuit/library/test_blueprintcircuit.py
index 2a5070e8ac74..5f0a2814872f 100644
--- a/test/python/circuit/library/test_blueprintcircuit.py
+++ b/test/python/circuit/library/test_blueprintcircuit.py
@@ -77,17 +77,17 @@ def test_invalidate_rebuild(self):
 
         with self.subTest(msg="after building"):
             self.assertGreater(len(mock._data), 0)
-            self.assertEqual(len(mock._parameter_table), 1)
+            self.assertEqual(mock._data.num_params(), 1)
 
         mock._invalidate()
         with self.subTest(msg="after invalidating"):
             self.assertFalse(mock._is_built)
-            self.assertEqual(len(mock._parameter_table), 0)
+            self.assertEqual(mock._data.num_params(), 0)
 
         mock._build()
         with self.subTest(msg="after re-building"):
             self.assertGreater(len(mock._data), 0)
-            self.assertEqual(len(mock._parameter_table), 1)
+            self.assertEqual(mock._data.num_params(), 1)
 
     def test_calling_attributes_works(self):
         """Test that the circuit is constructed when attributes are called."""
diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py
index 73398e4316bb..6fc6e8e72bd7 100644
--- a/test/python/circuit/test_circuit_data.py
+++ b/test/python/circuit/test_circuit_data.py
@@ -187,12 +187,20 @@ def test_foreach_op_indexed(self):
     def test_map_ops(self):
         """Test all operations are replaced."""
         qr = QuantumRegister(5)
+
+        # Use a custom gate to ensure we get a gate class returned and not
+        # a standard gate.
+        class CustomXGate(XGate):
+            """A custom X gate that doesn't have rust native representation."""
+
+            _standard_gate = None
+
         data_list = [
-            CircuitInstruction(XGate(), [qr[0]], []),
-            CircuitInstruction(XGate(), [qr[1]], []),
-            CircuitInstruction(XGate(), [qr[2]], []),
-            CircuitInstruction(XGate(), [qr[3]], []),
-            CircuitInstruction(XGate(), [qr[4]], []),
+            CircuitInstruction(CustomXGate(), [qr[0]], []),
+            CircuitInstruction(CustomXGate(), [qr[1]], []),
+            CircuitInstruction(CustomXGate(), [qr[2]], []),
+            CircuitInstruction(CustomXGate(), [qr[3]], []),
+            CircuitInstruction(CustomXGate(), [qr[4]], []),
         ]
         data = CircuitData(qubits=list(qr), data=data_list)
         data.map_ops(lambda op: op.to_mutable())
@@ -828,6 +836,9 @@ def test_param_gate_instance(self):
         qc0.append(rx, [0])
         qc1.append(rx, [0])
         qc0.assign_parameters({a: b}, inplace=True)
-        qc0_instance = next(iter(qc0._parameter_table[b]))[0]
-        qc1_instance = next(iter(qc1._parameter_table[a]))[0]
+        # A fancy way of doing qc0_instance = qc0.data[0] and qc1_instance = qc1.data[0]
+        # but this at least verifies the parameter table is point from the parameter to
+        # the correct instruction (which is the only one)
+        qc0_instance = qc0._data[next(iter(qc0._data._get_param(b.uuid.int)))[0]]
+        qc1_instance = qc1._data[next(iter(qc1._data._get_param(a.uuid.int)))[0]]
         self.assertNotEqual(qc0_instance, qc1_instance)
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index 6caf194d37d9..e9a7416f78c4 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -593,7 +593,7 @@ def test_clear_circuit(self):
         qc.clear()
 
         self.assertEqual(len(qc.data), 0)
-        self.assertEqual(len(qc._parameter_table), 0)
+        self.assertEqual(qc._data.num_params(), 0)
 
     def test_barrier(self):
         """Test multiple argument forms of barrier."""
diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py
index db6280b88230..7bb36a1401f8 100644
--- a/test/python/circuit/test_compose.py
+++ b/test/python/circuit/test_compose.py
@@ -357,7 +357,8 @@ def test_compose_copy(self):
         self.assertIsNot(should_copy.data[-1].operation, parametric.data[-1].operation)
         self.assertEqual(should_copy.data[-1].operation, parametric.data[-1].operation)
         forbid_copy = base.compose(parametric, qubits=[0], copy=False)
-        self.assertIs(forbid_copy.data[-1].operation, parametric.data[-1].operation)
+        # For standard gates a fresh copy is returned from the data list each time
+        self.assertEqual(forbid_copy.data[-1].operation, parametric.data[-1].operation)
 
         conditional = QuantumCircuit(1, 1)
         conditional.x(0).c_if(conditional.clbits[0], True)
diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py
index edd01c5cc1ce..4ac69278fd44 100644
--- a/test/python/circuit/test_instructions.py
+++ b/test/python/circuit/test_instructions.py
@@ -577,14 +577,14 @@ def test_instructionset_c_if_with_no_requester(self):
             instructions.add(instruction, [Qubit()], [])
             register = ClassicalRegister(2)
             instructions.c_if(register, 0)
-            self.assertIs(instruction.condition[0], register)
+            self.assertIs(instructions[0].operation.condition[0], register)
         with self.subTest("accepts arbitrary bit"):
             instruction = RZGate(0)
             instructions = InstructionSet()
             instructions.add(instruction, [Qubit()], [])
             bit = Clbit()
             instructions.c_if(bit, 0)
-            self.assertIs(instruction.condition[0], bit)
+            self.assertIs(instructions[0].operation.condition[0], bit)
         with self.subTest("rejects index"):
             instruction = RZGate(0)
             instructions = InstructionSet()
@@ -617,7 +617,7 @@ def dummy_requester(specifier):
             bit = Clbit()
             instructions.c_if(bit, 0)
             dummy_requester.assert_called_once_with(bit)
-            self.assertIs(instruction.condition[0], sentinel_bit)
+            self.assertIs(instructions[0].operation.condition[0], sentinel_bit)
         with self.subTest("calls requester with index"):
             dummy_requester.reset_mock()
             instruction = RZGate(0)
@@ -626,7 +626,7 @@ def dummy_requester(specifier):
             index = 0
             instructions.c_if(index, 0)
             dummy_requester.assert_called_once_with(index)
-            self.assertIs(instruction.condition[0], sentinel_bit)
+            self.assertIs(instructions[0].operation.condition[0], sentinel_bit)
         with self.subTest("calls requester with register"):
             dummy_requester.reset_mock()
             instruction = RZGate(0)
@@ -635,7 +635,7 @@ def dummy_requester(specifier):
             register = ClassicalRegister(2)
             instructions.c_if(register, 0)
             dummy_requester.assert_called_once_with(register)
-            self.assertIs(instruction.condition[0], sentinel_register)
+            self.assertIs(instructions[0].operation.condition[0], sentinel_register)
         with self.subTest("calls requester only once when broadcast"):
             dummy_requester.reset_mock()
             instruction_list = [RZGate(0), RZGate(0), RZGate(0)]
@@ -646,7 +646,7 @@ def dummy_requester(specifier):
             instructions.c_if(register, 0)
             dummy_requester.assert_called_once_with(register)
             for instruction in instruction_list:
-                self.assertIs(instruction.condition[0], sentinel_register)
+                self.assertIs(instructions[0].operation.condition[0], sentinel_register)
 
     def test_label_type_enforcement(self):
         """Test instruction label type enforcement."""
diff --git a/test/python/circuit/test_isometry.py b/test/python/circuit/test_isometry.py
index a09ff331e02a..35ff639cedd5 100644
--- a/test/python/circuit/test_isometry.py
+++ b/test/python/circuit/test_isometry.py
@@ -102,7 +102,6 @@ def test_isometry_tolerance(self, iso):
         # Simulate the decomposed gate
         unitary = Operator(qc).data
         iso_from_circuit = unitary[::, 0 : 2**num_q_input]
-
         self.assertTrue(np.allclose(iso_from_circuit, iso))
 
     @data(
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
index c86deee42876..fd63057cff8d 100644
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -26,7 +26,7 @@
 from qiskit.circuit.library.standard_gates.rz import RZGate
 from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
 from qiskit.circuit import Gate, Instruction, Parameter, ParameterExpression, ParameterVector
-from qiskit.circuit.parametertable import ParameterReferences, ParameterTable, ParameterView
+from qiskit.circuit.parametertable import ParameterView
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.compiler import assemble, transpile
 from qiskit import pulse
@@ -45,8 +45,6 @@ def raise_if_parameter_table_invalid(circuit):
        CircuitError: if QuantumCircuit and ParameterTable are inconsistent.
     """
 
-    table = circuit._parameter_table
-
     # Assert parameters present in circuit match those in table.
     circuit_parameters = {
         parameter
@@ -55,7 +53,7 @@ def raise_if_parameter_table_invalid(circuit):
         for parameter in param.parameters
         if isinstance(param, ParameterExpression)
     }
-    table_parameters = set(table._table.keys())
+    table_parameters = set(circuit._data.get_params_unsorted())
 
     if circuit_parameters != table_parameters:
         raise CircuitError(
@@ -67,8 +65,10 @@ def raise_if_parameter_table_invalid(circuit):
     # Assert parameter locations in table are present in circuit.
     circuit_instructions = [instr.operation for instr in circuit._data]
 
-    for parameter, instr_list in table.items():
-        for instr, param_index in instr_list:
+    for parameter in table_parameters:
+        instr_list = circuit._data._get_param(parameter.uuid.int)
+        for instr_index, param_index in instr_list:
+            instr = circuit.data[instr_index].operation
             if instr not in circuit_instructions:
                 raise CircuitError(f"ParameterTable instruction not present in circuit: {instr}.")
 
@@ -88,13 +88,15 @@ def raise_if_parameter_table_invalid(circuit):
                 )
 
     # Assert circuit has no other parameter locations other than those in table.
-    for instruction in circuit._data:
+    for instr_index, instruction in enumerate(circuit._data):
         for param_index, param in enumerate(instruction.operation.params):
             if isinstance(param, ParameterExpression):
                 parameters = param.parameters
 
                 for parameter in parameters:
-                    if (instruction.operation, param_index) not in table[parameter]:
+                    if (instr_index, param_index) not in circuit._data._get_param(
+                        parameter.uuid.int
+                    ):
                         raise CircuitError(
                             "Found parameterized instruction not "
                             "present in table. Instruction: {} "
@@ -158,15 +160,19 @@ def test_append_copies_parametric(self):
         self.assertIsNot(qc.data[-1].operation, gate_param)
         self.assertEqual(qc.data[-1].operation, gate_param)
 
+        # Standard gates are not stored as Python objects so a fresh object
+        # is always instantiated on accessing `CircuitInstruction.operation`
         qc.append(gate_param, [0], copy=False)
-        self.assertIs(qc.data[-1].operation, gate_param)
+        self.assertEqual(qc.data[-1].operation, gate_param)
 
         qc.append(gate_expr, [0], copy=True)
         self.assertIsNot(qc.data[-1].operation, gate_expr)
         self.assertEqual(qc.data[-1].operation, gate_expr)
 
+        # Standard gates are not stored as Python objects so a fresh object
+        # is always instantiated on accessing `CircuitInstruction.operation`
         qc.append(gate_expr, [0], copy=False)
-        self.assertIs(qc.data[-1].operation, gate_expr)
+        self.assertEqual(qc.data[-1].operation, gate_expr)
 
     def test_parameters_property(self):
         """Test instantiating gate with variable parameters"""
@@ -177,10 +183,9 @@ def test_parameters_property(self):
         qc = QuantumCircuit(qr)
         rxg = RXGate(theta)
         qc.append(rxg, [qr[0]], [])
-        vparams = qc._parameter_table
-        self.assertEqual(len(vparams), 1)
-        self.assertIs(theta, next(iter(vparams)))
-        self.assertEqual(rxg, next(iter(vparams[theta]))[0])
+        self.assertEqual(qc._data.num_params(), 1)
+        self.assertIs(theta, next(iter(qc._data.get_params_unsorted())))
+        self.assertEqual(rxg, qc.data[next(iter(qc._data._get_param(theta.uuid.int)))[0]].operation)
 
     def test_parameters_property_by_index(self):
         """Test getting parameters by index"""
@@ -553,12 +558,12 @@ def test_two_parameter_expression_binding(self):
         qc.rx(theta, 0)
         qc.ry(phi, 0)
 
-        self.assertEqual(len(qc._parameter_table[theta]), 1)
-        self.assertEqual(len(qc._parameter_table[phi]), 1)
+        self.assertEqual(qc._data._get_entry_count(theta), 1)
+        self.assertEqual(qc._data._get_entry_count(phi), 1)
 
         qc.assign_parameters({theta: -phi}, inplace=True)
 
-        self.assertEqual(len(qc._parameter_table[phi]), 2)
+        self.assertEqual(qc._data._get_entry_count(phi), 2)
 
     def test_expression_partial_binding_zero(self):
         """Verify that binding remains possible even if a previous partial bind
@@ -580,7 +585,6 @@ def test_expression_partial_binding_zero(self):
         fbqc = pqc.assign_parameters({phi: 1})
 
         self.assertEqual(fbqc.parameters, set())
-        self.assertIsInstance(fbqc.data[0].operation.params[0], int)
         self.assertEqual(float(fbqc.data[0].operation.params[0]), 0)
 
     def test_raise_if_assigning_params_not_in_circuit(self):
@@ -614,7 +618,7 @@ def test_gate_multiplicity_binding(self):
         qc.append(gate, [0], [])
         qc.append(gate, [0], [])
         qc2 = qc.assign_parameters({theta: 1.0})
-        self.assertEqual(len(qc2._parameter_table), 0)
+        self.assertEqual(qc2._data.num_params(), 0)
         for instruction in qc2.data:
             self.assertEqual(float(instruction.operation.params[0]), 1.0)
 
@@ -2170,155 +2174,6 @@ def test_parameter_symbol_equal_after_ufunc(self):
         self.assertEqual(phi._parameter_symbols, cos_phi._parameter_symbols)
 
 
-class TestParameterReferences(QiskitTestCase):
-    """Test the ParameterReferences class."""
-
-    def test_equal_inst_diff_instance(self):
-        """Different value equal instructions are treated as distinct."""
-
-        theta = Parameter("theta")
-        gate1 = RZGate(theta)
-        gate2 = RZGate(theta)
-
-        self.assertIsNot(gate1, gate2)
-        self.assertEqual(gate1, gate2)
-
-        refs = ParameterReferences(((gate1, 0), (gate2, 0)))
-
-        # test __contains__
-        self.assertIn((gate1, 0), refs)
-        self.assertIn((gate2, 0), refs)
-
-        gate_ids = {id(gate1), id(gate2)}
-        self.assertEqual(gate_ids, {id(gate) for gate, _ in refs})
-        self.assertTrue(all(idx == 0 for _, idx in refs))
-
-    def test_pickle_unpickle(self):
-        """Membership testing after pickle/unpickle."""
-
-        theta = Parameter("theta")
-        gate1 = RZGate(theta)
-        gate2 = RZGate(theta)
-
-        self.assertIsNot(gate1, gate2)
-        self.assertEqual(gate1, gate2)
-
-        refs = ParameterReferences(((gate1, 0), (gate2, 0)))
-
-        to_pickle = (gate1, refs)
-        pickled = pickle.dumps(to_pickle)
-        (gate1_new, refs_new) = pickle.loads(pickled)
-
-        self.assertEqual(len(refs_new), len(refs))
-        self.assertNotIn((gate1, 0), refs_new)
-        self.assertIn((gate1_new, 0), refs_new)
-
-    def test_equal_inst_same_instance(self):
-        """Referentially equal instructions are treated as same."""
-
-        theta = Parameter("theta")
-        gate = RZGate(theta)
-
-        refs = ParameterReferences(((gate, 0), (gate, 0)))
-
-        self.assertIn((gate, 0), refs)
-        self.assertEqual(len(refs), 1)
-        self.assertIs(next(iter(refs))[0], gate)
-        self.assertEqual(next(iter(refs))[1], 0)
-
-    def test_extend_refs(self):
-        """Extending references handles duplicates."""
-
-        theta = Parameter("theta")
-        ref0 = (RZGate(theta), 0)
-        ref1 = (RZGate(theta), 0)
-        ref2 = (RZGate(theta), 0)
-
-        refs = ParameterReferences((ref0,))
-        refs |= ParameterReferences((ref0, ref1, ref2, ref1, ref0))
-
-        self.assertEqual(refs, ParameterReferences((ref0, ref1, ref2)))
-
-    def test_copy_param_refs(self):
-        """Copy of parameter references is a shallow copy."""
-
-        theta = Parameter("theta")
-        ref0 = (RZGate(theta), 0)
-        ref1 = (RZGate(theta), 0)
-        ref2 = (RZGate(theta), 0)
-        ref3 = (RZGate(theta), 0)
-
-        refs = ParameterReferences((ref0, ref1))
-        refs_copy = refs.copy()
-
-        # Check same gate instances in copy
-        gate_ids = {id(ref0[0]), id(ref1[0])}
-        self.assertEqual({id(gate) for gate, _ in refs_copy}, gate_ids)
-
-        # add new ref to original and check copy not modified
-        refs.add(ref2)
-        self.assertNotIn(ref2, refs_copy)
-        self.assertEqual(refs_copy, ParameterReferences((ref0, ref1)))
-
-        # add new ref to copy and check original not modified
-        refs_copy.add(ref3)
-        self.assertNotIn(ref3, refs)
-        self.assertEqual(refs, ParameterReferences((ref0, ref1, ref2)))
-
-
-class TestParameterTable(QiskitTestCase):
-    """Test the ParameterTable class."""
-
-    def test_init_param_table(self):
-        """Parameter table init from mapping."""
-
-        p1 = Parameter("theta")
-        p2 = Parameter("theta")
-
-        ref0 = (RZGate(p1), 0)
-        ref1 = (RZGate(p1), 0)
-        ref2 = (RZGate(p2), 0)
-
-        mapping = {p1: ParameterReferences((ref0, ref1)), p2: ParameterReferences((ref2,))}
-
-        table = ParameterTable(mapping)
-
-        # make sure editing mapping doesn't change `table`
-        del mapping[p1]
-
-        self.assertEqual(table[p1], ParameterReferences((ref0, ref1)))
-        self.assertEqual(table[p2], ParameterReferences((ref2,)))
-
-    def test_set_references(self):
-        """References replacement by parameter key."""
-
-        p1 = Parameter("theta")
-
-        ref0 = (RZGate(p1), 0)
-        ref1 = (RZGate(p1), 0)
-
-        table = ParameterTable()
-        table[p1] = ParameterReferences((ref0, ref1))
-        self.assertEqual(table[p1], ParameterReferences((ref0, ref1)))
-
-        table[p1] = ParameterReferences((ref1,))
-        self.assertEqual(table[p1], ParameterReferences((ref1,)))
-
-    def test_set_references_from_iterable(self):
-        """Parameter table init from iterable."""
-
-        p1 = Parameter("theta")
-
-        ref0 = (RZGate(p1), 0)
-        ref1 = (RZGate(p1), 0)
-        ref2 = (RZGate(p1), 0)
-
-        table = ParameterTable({p1: ParameterReferences((ref0, ref1))})
-        table[p1] = (ref2, ref1, ref0)
-
-        self.assertEqual(table[p1], ParameterReferences((ref2, ref1, ref0)))
-
-
 class TestParameterView(QiskitTestCase):
     """Test the ParameterView object."""
 
diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py
new file mode 100644
index 000000000000..06d4ed86a60a
--- /dev/null
+++ b/test/python/circuit/test_rust_equivalence.py
@@ -0,0 +1,143 @@
+# 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.
+
+"""Rust gate definition tests"""
+
+from math import pi
+
+from test import QiskitTestCase
+
+import numpy as np
+
+from qiskit.circuit import QuantumCircuit
+from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
+
+SKIP_LIST = {"cy", "ccx", "rx", "ry", "ecr", "sx"}
+CUSTOM_MAPPING = {"x", "rz"}
+
+
+class TestRustGateEquivalence(QiskitTestCase):
+    """Tests that compile time rust gate definitions is correct."""
+
+    def setUp(self):
+        super().setUp()
+        self.standard_gates = get_standard_gate_name_mapping()
+        # Pre-warm gate mapping cache, this is needed so rust -> py conversion is done
+        qc = QuantumCircuit(3)
+        for gate in self.standard_gates.values():
+            if getattr(gate, "_standard_gate", None):
+                if gate.params:
+                    gate = gate.base_class(*[pi] * len(gate.params))
+                qc.append(gate, list(range(gate.num_qubits)))
+
+    def test_definitions(self):
+        """Test definitions are the same in rust space."""
+        for name, gate_class in self.standard_gates.items():
+            standard_gate = getattr(gate_class, "_standard_gate", None)
+            if name in SKIP_LIST:
+                # gate does not have a rust definition yet
+                continue
+            if standard_gate is None:
+                # gate is not in rust yet
+                continue
+
+            with self.subTest(name=name):
+                params = [pi] * standard_gate._num_params()
+                py_def = gate_class.base_class(*params).definition
+                rs_def = standard_gate._get_definition(params)
+                if py_def is None:
+                    self.assertIsNone(rs_def)
+                else:
+                    rs_def = QuantumCircuit._from_circuit_data(rs_def)
+                    for rs_inst, py_inst in zip(rs_def._data, py_def._data):
+                        # Rust uses U but python still uses U3 and u2
+                        if rs_inst.operation.name == "u":
+                            if py_inst.operation.name == "u3":
+                                self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
+                            elif py_inst.operation.name == "u2":
+                                self.assertEqual(
+                                    rs_inst.operation.params,
+                                    [
+                                        pi / 2,
+                                        py_inst.operation.params[0],
+                                        py_inst.operation.params[1],
+                                    ],
+                                )
+
+                            self.assertEqual(
+                                [py_def.find_bit(x).index for x in py_inst.qubits],
+                                [rs_def.find_bit(x).index for x in rs_inst.qubits],
+                            )
+                        # Rust uses P but python still uses u1
+                        elif rs_inst.operation.name == "p":
+                            self.assertEqual(py_inst.operation.name, "u1")
+                            self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
+                            self.assertEqual(
+                                [py_def.find_bit(x).index for x in py_inst.qubits],
+                                [rs_def.find_bit(x).index for x in rs_inst.qubits],
+                            )
+                        else:
+                            self.assertEqual(py_inst.operation.name, rs_inst.operation.name)
+                            self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
+                            self.assertEqual(
+                                [py_def.find_bit(x).index for x in py_inst.qubits],
+                                [rs_def.find_bit(x).index for x in rs_inst.qubits],
+                            )
+
+    def test_matrix(self):
+        """Test matrices are the same in rust space."""
+        for name, gate_class in self.standard_gates.items():
+            standard_gate = getattr(gate_class, "_standard_gate", None)
+            if standard_gate is None:
+                # gate is not in rust yet
+                continue
+
+            with self.subTest(name=name):
+                params = [pi] * standard_gate._num_params()
+                py_def = gate_class.base_class(*params).to_matrix()
+                rs_def = standard_gate._to_matrix(params)
+                np.testing.assert_allclose(rs_def, py_def)
+
+    def test_name(self):
+        """Test that the gate name properties match in rust space."""
+        for name, gate_class in self.standard_gates.items():
+            standard_gate = getattr(gate_class, "_standard_gate", None)
+            if standard_gate is None:
+                # gate is not in rust yet
+                continue
+
+            with self.subTest(name=name):
+                self.assertEqual(gate_class.name, standard_gate.name)
+
+    def test_num_qubits(self):
+        """Test the number of qubits are the same in rust space."""
+        for name, gate_class in self.standard_gates.items():
+            standard_gate = getattr(gate_class, "_standard_gate", None)
+            if standard_gate is None:
+                # gate is not in rust yet
+                continue
+
+            with self.subTest(name=name):
+                self.assertEqual(gate_class.num_qubits, standard_gate.num_qubits)
+
+    def test_num_params(self):
+        """Test the number of parameters are the same in rust space."""
+        for name, gate_class in self.standard_gates.items():
+            standard_gate = getattr(gate_class, "_standard_gate", None)
+            if standard_gate is None:
+                # gate is not in rust yet
+                continue
+
+            with self.subTest(name=name):
+                self.assertEqual(
+                    len(gate_class.params), standard_gate.num_params, msg=f"{name} not equal"
+                )
diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py
index 598405beaae1..135e874be488 100644
--- a/test/python/qasm3/test_export.py
+++ b/test/python/qasm3/test_export.py
@@ -1946,7 +1946,7 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
     def test_basis_gates(self):
         """Teleportation with physical qubits"""
         qc = QuantumCircuit(3, 2)
-        first_h = qc.h(1)[0].operation
+        qc.h(1)
         qc.cx(1, 2)
         qc.barrier()
         qc.cx(0, 1)
@@ -1957,52 +1957,51 @@ def test_basis_gates(self):
         first_x = qc.x(2).c_if(qc.clbits[1], 1)[0].operation
         qc.z(2).c_if(qc.clbits[0], 1)
 
-        u2 = first_h.definition.data[0].operation
-        u3_1 = u2.definition.data[0].operation
-        u3_2 = first_x.definition.data[0].operation
-
-        expected_qasm = "\n".join(
-            [
-                "OPENQASM 3.0;",
-                f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
-                "  U(pi/2, 0, pi) _gate_q_0;",
-                "}",
-                f"gate u2_{id(u2)}(_gate_p_0, _gate_p_1) _gate_q_0 {{",
-                f"  u3_{id(u3_1)}(pi/2, 0, pi) _gate_q_0;",
-                "}",
-                "gate h _gate_q_0 {",
-                f"  u2_{id(u2)}(0, pi) _gate_q_0;",
-                "}",
-                f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
-                "  U(pi, 0, pi) _gate_q_0;",
-                "}",
-                "gate x _gate_q_0 {",
-                f"  u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;",
-                "}",
-                "bit[2] c;",
-                "qubit[3] q;",
-                "h q[1];",
-                "cx q[1], q[2];",
-                "barrier q[0], q[1], q[2];",
-                "cx q[0], q[1];",
-                "h q[0];",
-                "barrier q[0], q[1], q[2];",
-                "c[0] = measure q[0];",
-                "c[1] = measure q[1];",
-                "barrier q[0], q[1], q[2];",
-                "if (c[1]) {",
-                "  x q[2];",
-                "}",
-                "if (c[0]) {",
-                "  z q[2];",
-                "}",
-                "",
-            ]
-        )
-        self.assertEqual(
-            Exporter(includes=[], basis_gates=["cx", "z", "U"]).dumps(qc),
-            expected_qasm,
-        )
+        id_len = len(str(id(first_x)))
+        expected_qasm = [
+            "OPENQASM 3.0;",
+            re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
+            "  U(pi/2, 0, pi) _gate_q_0;",
+            "}",
+            re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len),
+            re.compile(r"  u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len),
+            "}",
+            "gate h _gate_q_0 {",
+            re.compile(r"  u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len),
+            "}",
+            re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
+            "  U(pi, 0, pi) _gate_q_0;",
+            "}",
+            "gate x _gate_q_0 {",
+            re.compile(r"  u3_\d{%s}\(pi, 0, pi\) _gate_q_0;" % id_len),
+            "}",
+            "bit[2] c;",
+            "qubit[3] q;",
+            "h q[1];",
+            "cx q[1], q[2];",
+            "barrier q[0], q[1], q[2];",
+            "cx q[0], q[1];",
+            "h q[0];",
+            "barrier q[0], q[1], q[2];",
+            "c[0] = measure q[0];",
+            "c[1] = measure q[1];",
+            "barrier q[0], q[1], q[2];",
+            "if (c[1]) {",
+            "  x q[2];",
+            "}",
+            "if (c[0]) {",
+            "  z q[2];",
+            "}",
+            "",
+        ]
+        res = Exporter(includes=[], basis_gates=["cx", "z", "U"]).dumps(qc).splitlines()
+        for result, expected in zip(res, expected_qasm):
+            if isinstance(expected, str):
+                self.assertEqual(result, expected)
+            else:
+                self.assertTrue(
+                    expected.search(result), f"Line {result} doesn't match regex: {expected}"
+                )
 
     def test_teleportation(self):
         """Teleportation with physical qubits"""
@@ -2120,62 +2119,58 @@ def test_no_include(self):
         circuit.sx(0)
         circuit.cx(0, 1)
 
-        rz = circuit.data[0].operation
-        u1_1 = rz.definition.data[0].operation
-        u3_1 = u1_1.definition.data[0].operation
-        sx = circuit.data[1].operation
-        sdg = sx.definition.data[0].operation
-        u1_2 = sdg.definition.data[0].operation
-        u3_2 = u1_2.definition.data[0].operation
-        h_ = sx.definition.data[1].operation
-        u2_1 = h_.definition.data[0].operation
-        u3_3 = u2_1.definition.data[0].operation
-        expected_qasm = "\n".join(
-            [
-                "OPENQASM 3.0;",
-                f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
-                "  U(0, 0, pi/2) _gate_q_0;",
-                "}",
-                f"gate u1_{id(u1_1)}(_gate_p_0) _gate_q_0 {{",
-                f"  u3_{id(u3_1)}(0, 0, pi/2) _gate_q_0;",
-                "}",
-                f"gate rz_{id(rz)}(_gate_p_0) _gate_q_0 {{",
-                f"  u1_{id(u1_1)}(pi/2) _gate_q_0;",
-                "}",
-                f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
-                "  U(0, 0, -pi/2) _gate_q_0;",
-                "}",
-                f"gate u1_{id(u1_2)}(_gate_p_0) _gate_q_0 {{",
-                f"  u3_{id(u3_2)}(0, 0, -pi/2) _gate_q_0;",
-                "}",
-                "gate sdg _gate_q_0 {",
-                f"  u1_{id(u1_2)}(-pi/2) _gate_q_0;",
-                "}",
-                f"gate u3_{id(u3_3)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
-                "  U(pi/2, 0, pi) _gate_q_0;",
-                "}",
-                f"gate u2_{id(u2_1)}(_gate_p_0, _gate_p_1) _gate_q_0 {{",
-                f"  u3_{id(u3_3)}(pi/2, 0, pi) _gate_q_0;",
-                "}",
-                "gate h _gate_q_0 {",
-                f"  u2_{id(u2_1)}(0, pi) _gate_q_0;",
-                "}",
-                "gate sx _gate_q_0 {",
-                "  sdg _gate_q_0;",
-                "  h _gate_q_0;",
-                "  sdg _gate_q_0;",
-                "}",
-                "gate cx c, t {",
-                "  ctrl @ U(pi, 0, pi) c, t;",
-                "}",
-                "qubit[2] q;",
-                f"rz_{id(rz)}(pi/2) q[0];",
-                "sx q[0];",
-                "cx q[0], q[1];",
-                "",
-            ]
-        )
-        self.assertEqual(Exporter(includes=[]).dumps(circuit), expected_qasm)
+        id_len = len(str(id(circuit.data[0].operation)))
+        expected_qasm = [
+            "OPENQASM 3.0;",
+            re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
+            "  U(0, 0, pi/2) _gate_q_0;",
+            "}",
+            re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
+            re.compile(r"  u3_\d{%s}\(0, 0, pi/2\) _gate_q_0;" % id_len),
+            "}",
+            re.compile(r"gate rz_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
+            re.compile(r"  u1_\d{%s}\(pi/2\) _gate_q_0;" % id_len),
+            "}",
+            re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
+            "  U(0, 0, -pi/2) _gate_q_0;",
+            "}",
+            re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
+            re.compile(r"  u3_\d{%s}\(0, 0, -pi/2\) _gate_q_0;" % id_len),
+            "}",
+            "gate sdg _gate_q_0 {",
+            re.compile(r"  u1_\d{%s}\(-pi/2\) _gate_q_0;" % id_len),
+            "}",
+            re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
+            "  U(pi/2, 0, pi) _gate_q_0;",
+            "}",
+            re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len),
+            re.compile(r"  u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len),
+            "}",
+            "gate h _gate_q_0 {",
+            re.compile(r"  u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len),
+            "}",
+            "gate sx _gate_q_0 {",
+            "  sdg _gate_q_0;",
+            "  h _gate_q_0;",
+            "  sdg _gate_q_0;",
+            "}",
+            "gate cx c, t {",
+            "  ctrl @ U(pi, 0, pi) c, t;",
+            "}",
+            "qubit[2] q;",
+            re.compile(r"rz_\d{%s}\(pi/2\) q\[0\];" % id_len),
+            "sx q[0];",
+            "cx q[0], q[1];",
+            "",
+        ]
+        res = Exporter(includes=[]).dumps(circuit).splitlines()
+        for result, expected in zip(res, expected_qasm):
+            if isinstance(expected, str):
+                self.assertEqual(result, expected)
+            else:
+                self.assertTrue(
+                    expected.search(result), f"Line {result} doesn't match regex: {expected}"
+                )
 
     def test_unusual_conditions(self):
         """Test that special QASM constructs such as ``measure`` are correctly handled when the

From 2d6a5a2bbb3f330fe921120087358c3fd790d537 Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Thu, 13 Jun 2024 13:11:26 +0200
Subject: [PATCH 147/179] exclude lines from coverage report (#12564)

* exclude lines from coverage

* comments with explanation

* to pyproject from tox
---
 pyproject.toml           | 9 +++++++++
 qiskit/qasm3/exporter.py | 4 ++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 149d6c0f2d47..0740861c98c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -231,3 +231,12 @@ enable = [
 
 [tool.pylint.spelling]
 spelling-private-dict-file = ".local-spellings"
+
+[tool.coverage.report]
+exclude_also = [
+    "def __repr__",               # Printable epresentational string does not typically execute during testing
+    "raise NotImplementedError",  # Abstract methods are not testable
+    "raise RuntimeError",         # Exceptions for defensive programming that cannot be tested a head
+    "if TYPE_CHECKING:",          # Code that only runs during type checks
+    "@abstractmethod",            # Abstract methods are not testable
+    ]
diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py
index 7f737af76eb4..78b992b17cf2 100644
--- a/qiskit/qasm3/exporter.py
+++ b/qiskit/qasm3/exporter.py
@@ -842,7 +842,7 @@ def build_current_scope(self) -> List[ast.Statement]:
                     statements.append(self.build_if_statement(instruction))
                 elif isinstance(instruction.operation, SwitchCaseOp):
                     statements.extend(self.build_switch_statement(instruction))
-                else:  # pragma: no cover
+                else:
                     raise RuntimeError(f"unhandled control-flow construct: {instruction.operation}")
                 continue
             # Build the node, ignoring any condition.
@@ -1136,7 +1136,7 @@ def _build_ast_type(type_: types.Type) -> ast.ClassicalType:
         return ast.BoolType()
     if type_.kind is types.Uint:
         return ast.UintType(type_.width)
-    raise RuntimeError(f"unhandled expr type '{type_}'")  # pragma: no cover
+    raise RuntimeError(f"unhandled expr type '{type_}'")
 
 
 class _ExprBuilder(expr.ExprVisitor[ast.Expression]):

From 81433d53058a207ba66a300180664cfdfb1725fa Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 13 Jun 2024 16:20:17 +0000
Subject: [PATCH 148/179] Bump pypa/cibuildwheel in the github_actions group
 across 1 directory (#12568)

Bumps the github_actions group with 1 update in the / directory: [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel).


Updates `pypa/cibuildwheel` from 2.17.0 to 2.19.1
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v2.17.0...v2.19.1)

---
updated-dependencies:
- dependency-name: pypa/cibuildwheel
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_actions
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/wheels.yml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 2cd3c8ac0a39..7c29f1376e46 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -30,7 +30,7 @@ jobs:
         with:
           components: llvm-tools-preview
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_BEFORE_BUILD: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata'
           CIBW_BEFORE_BUILD_WINDOWS: 'bash ./tools/build_pgo.sh /tmp/pgo-data/merged.profdata && cp /tmp/pgo-data/merged.profdata ~/.'
@@ -58,7 +58,7 @@ jobs:
         with:
           components: llvm-tools-preview
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_BEFORE_ALL: rustup target add aarch64-apple-darwin
           CIBW_BUILD: cp38-macosx_universal2 cp38-macosx_arm64
@@ -87,7 +87,7 @@ jobs:
         with:
           components: llvm-tools-preview
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_SKIP: 'pp* cp36-* cp37-* *musllinux* *amd64 *x86_64'
       - uses: actions/upload-artifact@v4
@@ -133,7 +133,7 @@ jobs:
         with:
           platforms: all
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_ARCHS_LINUX: s390x
           CIBW_TEST_SKIP: "cp*"
@@ -167,7 +167,7 @@ jobs:
         with:
           platforms: all
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_ARCHS_LINUX: ppc64le
           CIBW_TEST_SKIP: "cp*"
@@ -201,7 +201,7 @@ jobs:
         with:
           platforms: all
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.17.0
+        uses: pypa/cibuildwheel@v2.19.1
         env:
           CIBW_ARCHS_LINUX: aarch64
       - uses: actions/upload-artifact@v4

From cd2c65d773895a2e8a57568ad490463ccd0122c6 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Fri, 14 Jun 2024 11:32:03 +0200
Subject: [PATCH 149/179] Bump version of macOS used on Azure (#12571)

The macOS 11 runners are deprecated pending removal.  While macOS 14 is
available, it's still marked as in preview on Azure, so macOS 13 is the
current "latest" stable version.
---
 .azure/test-macos.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.azure/test-macos.yml b/.azure/test-macos.yml
index 1887a88655d0..b167df3f1c82 100644
--- a/.azure/test-macos.yml
+++ b/.azure/test-macos.yml
@@ -10,7 +10,7 @@ parameters:
 jobs:
   - job: "MacOS_Tests_Python${{ replace(parameters.pythonVersion, '.', '') }}"
     displayName: "Test macOS Python ${{ parameters.pythonVersion }}"
-    pool: {vmImage: 'macOS-11'}
+    pool: {vmImage: 'macOS-13'}
 
     variables:
       QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y

From 17ab36478689200f6a661f731d13c24473031a30 Mon Sep 17 00:00:00 2001
From: Catherine Lozano 
Date: Mon, 17 Jun 2024 09:52:53 -0400
Subject: [PATCH 150/179] += depreciated changed to &= (#12515)

* += depreciated

* fixed deprecated += to &= in two_local
---
 qiskit/circuit/library/n_local/two_local.py | 2 +-
 test/benchmarks/utils.py                    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit/circuit/library/n_local/two_local.py b/qiskit/circuit/library/n_local/two_local.py
index 61b9e725cc6d..1cb388349fec 100644
--- a/qiskit/circuit/library/n_local/two_local.py
+++ b/qiskit/circuit/library/n_local/two_local.py
@@ -87,7 +87,7 @@ class TwoLocal(NLocal):
 
         >>> two = TwoLocal(3, ['ry','rz'], 'cz', 'full', reps=1, insert_barriers=True)
         >>> qc = QuantumCircuit(3)
-        >>> qc += two
+        >>> qc &= two
         >>> print(qc.decompose().draw())
              ┌──────────┐┌──────────┐ ░           ░ ┌──────────┐ ┌──────────┐
         q_0: ┤ Ry(θ[0]) ├┤ Rz(θ[3]) ├─░──■──■─────░─┤ Ry(θ[6]) ├─┤ Rz(θ[9]) ├
diff --git a/test/benchmarks/utils.py b/test/benchmarks/utils.py
index bbd7d0a9af85..13350346b827 100644
--- a/test/benchmarks/utils.py
+++ b/test/benchmarks/utils.py
@@ -215,7 +215,7 @@ def unmajority(p, a, b, c):
     qc.x(a[0])  # Set input a = 0...0001
     qc.x(b)  # Set input b = 1...1111
     # Apply the adder
-    qc += adder_subcircuit
+    qc &= adder_subcircuit
 
     # Measure the output register in the computational basis
     for j in range(n):

From 0bca3c403bb20ada4fef8f17d0affcf93ebcbefb Mon Sep 17 00:00:00 2001
From: Luciano Bello 
Date: Mon, 17 Jun 2024 17:20:58 +0200
Subject: [PATCH 151/179] Remove extra parenthesis in Primitive examples
 (#12587)

* remove extra parenthesis

* Update qiskit/primitives/__init__.py

---------

Co-authored-by: Julien Gacon 
---
 qiskit/primitives/__init__.py | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/qiskit/primitives/__init__.py b/qiskit/primitives/__init__.py
index 569c075b23be..7cf8355354e3 100644
--- a/qiskit/primitives/__init__.py
+++ b/qiskit/primitives/__init__.py
@@ -51,7 +51,7 @@
 * a collection parameter value sets to bind the circuit against, :math:`\theta_k`.
 
 Running an estimator returns a :class:`~qiskit.primitives.BasePrimitiveJob` object, where calling
-the method :meth:`~qiskit.primitives.BasePrimitiveJob.result` results in expectation value estimates 
+the method :meth:`~qiskit.primitives.BasePrimitiveJob.result` results in expectation value estimates
 and metadata for each pub:
 
 .. math::
@@ -95,7 +95,7 @@
     #             [] ]
     job2 = estimator.run(
         [
-            (psi1, [H1, H3], [theta1, theta3]), 
+            (psi1, [H1, H3], [theta1, theta3]),
             (psi2, H2, theta2)
         ],
         precision=0.01
@@ -103,7 +103,7 @@
     job_result = job2.result()
     print(f"The primitive-job finished with result {job_result}")
 
-    
+
 Overview of SamplerV2
 =====================
 
@@ -153,12 +153,12 @@
     # collect 128 shots from the Bell circuit
     job = sampler.run([bell], shots=128)
     job_result = job.result()
-    print(f"The primitive-job finished with result {job_result}"))
+    print(f"The primitive-job finished with result {job_result}")
 
     # run a sampler job on the parameterized circuits
-    job2 = sampler.run([(pqc, theta1), (pqc2, theta2)]
+    job2 = sampler.run([(pqc, theta1), (pqc2, theta2)])
     job_result = job2.result()
-    print(f"The primitive-job finished with result {job_result}"))
+    print(f"The primitive-job finished with result {job_result}")
 
 
 Overview of EstimatorV1
@@ -214,14 +214,14 @@
     # calculate [  ]
     job = estimator.run([psi1], [H1], [theta1])
     job_result = job.result() # It will block until the job finishes.
-    print(f"The primitive-job finished with result {job_result}"))
+    print(f"The primitive-job finished with result {job_result}")
 
     # calculate [ ,
     #             ,
     #              ]
     job2 = estimator.run(
-        [psi1, psi2, psi1], 
-        [H1, H2, H3], 
+        [psi1, psi2, psi1],
+        [H1, H2, H3],
         [theta1, theta2, theta3]
     )
     job_result = job2.result()

From 864a2a3c68d6901ac0209b0a5fd02344430c79d3 Mon Sep 17 00:00:00 2001
From: Catherine Lozano 
Date: Mon, 17 Jun 2024 11:59:02 -0400
Subject: [PATCH 152/179] Fixed incorrect behavior of scheduling benchmarks
 (#12524)

* Setting conditional and reset to false as not supported in alap/asap

* removed deprecated test

* removed unused dag
---
 test/benchmarks/scheduling_passes.py | 20 +++-----------------
 1 file changed, 3 insertions(+), 17 deletions(-)

diff --git a/test/benchmarks/scheduling_passes.py b/test/benchmarks/scheduling_passes.py
index a4c25dc46bcb..34d40ea97e63 100644
--- a/test/benchmarks/scheduling_passes.py
+++ b/test/benchmarks/scheduling_passes.py
@@ -37,7 +37,7 @@ class SchedulingPassBenchmarks:
     def setup(self, n_qubits, depth):
         seed = 42
         self.circuit = random_circuit(
-            n_qubits, depth, measure=True, conditional=True, reset=True, seed=seed, max_operands=2
+            n_qubits, depth, measure=True, conditional=False, reset=False, seed=seed, max_operands=2
         )
         self.basis_gates = ["rz", "sx", "x", "cx", "id", "reset"]
         self.cmap = [
@@ -108,15 +108,6 @@ def setup(self, n_qubits, depth):
             ],
             dt=1e-9,
         )
-        self.timed_dag = TimeUnitConversion(self.durations).run(self.dag)
-        dd_sequence = [XGate(), XGate()]
-        pm = PassManager(
-            [
-                ALAPScheduleAnalysis(self.durations),
-                PadDynamicalDecoupling(self.durations, dd_sequence),
-            ]
-        )
-        self.scheduled_dag = pm.run(self.timed_dag)
 
     def time_time_unit_conversion_pass(self, _, __):
         TimeUnitConversion(self.durations).run(self.dag)
@@ -129,7 +120,7 @@ def time_alap_schedule_pass(self, _, __):
                 PadDynamicalDecoupling(self.durations, dd_sequence),
             ]
         )
-        pm.run(self.timed_dag)
+        pm.run(self.transpiled_circuit)
 
     def time_asap_schedule_pass(self, _, __):
         dd_sequence = [XGate(), XGate()]
@@ -139,9 +130,4 @@ def time_asap_schedule_pass(self, _, __):
                 PadDynamicalDecoupling(self.durations, dd_sequence),
             ]
         )
-        pm.run(self.timed_dag)
-
-    def time_dynamical_decoupling_pass(self, _, __):
-        PadDynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run(
-            self.scheduled_dag
-        )
+        pm.run(self.transpiled_circuit)

From 6d6dce327264201d7158baf6af2152bbe387fc57 Mon Sep 17 00:00:00 2001
From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
Date: Tue, 18 Jun 2024 05:12:04 -0400
Subject: [PATCH 153/179] Remove Eric from Rust bot notifications (#12596)

---
 qiskit_bot.yaml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml
index 2467665e0d00..edff5997c8fe 100644
--- a/qiskit_bot.yaml
+++ b/qiskit_bot.yaml
@@ -28,7 +28,6 @@ notifications:
     ".*\\.rs$|^Cargo":
         - "`@mtreinish`"
         - "`@kevinhartman`"
-        - "@Eric-Arellano"
     "(?!.*pulse.*)\\bvisualization\\b":
         - "@enavarro51"
     "^docs/":

From d4e795b43146b01103df608d1cf55425b4bfd765 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?=
 <57907331+ElePT@users.noreply.github.com>
Date: Tue, 18 Jun 2024 21:28:59 +0200
Subject: [PATCH 154/179] Add rust representation for SGates, TGates, and iSwap
 gate (#12598)

* Add SGate, SdgGate, iSWAP, TGate, and TdgGate to standard gates in rust.
Add missing gate definitions that depended on these gates (marked as todo).

* Add fast path to circuit methods, fix sneaky bugs, unskip cy and sx tests.

* Unskip ccx test too!

* Fix black
---
 crates/circuit/src/gate_matrix.rs             |  24 ++
 crates/circuit/src/imports.rs                 |  14 +-
 crates/circuit/src/operations.rs              | 220 ++++++++++++++++--
 .../circuit/library/standard_gates/iswap.py   |   3 +
 qiskit/circuit/library/standard_gates/s.py    |   5 +
 qiskit/circuit/library/standard_gates/sx.py   |   2 +
 qiskit/circuit/library/standard_gates/t.py    |   5 +
 qiskit/circuit/quantumcircuit.py              |  24 +-
 test/python/circuit/test_rust_equivalence.py  |   2 +-
 9 files changed, 265 insertions(+), 34 deletions(-)

diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs
index 72e1087637c0..ad8c918e73bc 100644
--- a/crates/circuit/src/gate_matrix.rs
+++ b/crates/circuit/src/gate_matrix.rs
@@ -63,6 +63,11 @@ pub static SX_GATE: [[Complex64; 2]; 2] = [
     [c64(0.5, -0.5), c64(0.5, 0.5)],
 ];
 
+pub static SXDG_GATE: [[Complex64; 2]; 2] = [
+    [c64(0.5, -0.5), c64(0.5, 0.5)],
+    [c64(0.5, 0.5), c64(0.5, -0.5)],
+];
+
 pub static X_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(1., 0.)], [c64(1., 0.), c64(0., 0.)]];
 
 pub static Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]];
@@ -199,6 +204,25 @@ pub static SWAP_GATE: [[Complex64; 4]; 4] = [
     [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
     [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
 ];
+pub static ISWAP_GATE: [[Complex64; 4]; 4] = [
+    [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 1.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
+];
+
+pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]];
+pub static SDG_GATE: [[Complex64; 2]; 2] =
+    [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]];
+
+pub static T_GATE: [[Complex64; 2]; 2] = [
+    [c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)],
+];
+pub static TDG_GATE: [[Complex64; 2]; 2] = [
+    [c64(1., 0.), c64(0., 0.)],
+    [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)],
+];
 
 #[inline]
 pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] {
diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs
index 050f7f2e053c..8db3b88fd7d2 100644
--- a/crates/circuit/src/imports.rs
+++ b/crates/circuit/src/imports.rs
@@ -77,7 +77,7 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell =
 /// when a gate is added directly via the StandardGate path and there isn't a Python object
 /// to poll the _standard_gate attribute for.
 ///
-/// NOTE: the order here is significant it must match the StandardGate variant's number must match
+/// NOTE: the order here is significant, the StandardGate variant's number must match
 /// index of it's entry in this table. This is all done statically for performance
 static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
     // ZGate = 0
@@ -119,6 +119,18 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
     ["qiskit.circuit.library.standard_gates.p", "PhaseGate"],
     // UGate = 17
     ["qiskit.circuit.library.standard_gates.u", "UGate"],
+    // SGate = 18
+    ["qiskit.circuit.library.standard_gates.s", "SGate"],
+    // SdgGate = 19
+    ["qiskit.circuit.library.standard_gates.s", "SdgGate"],
+    // TGate = 20
+    ["qiskit.circuit.library.standard_gates.s", "TGate"],
+    // TdgGate = 21
+    ["qiskit.circuit.library.standard_gates.s", "TdgGate"],
+    // SXdgGate = 22
+    ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"],
+    // iSWAPGate = 23
+    ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"],
 ];
 
 /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the
diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs
index ead1b8ee1ebb..9048c55d9d48 100644
--- a/crates/circuit/src/operations.rs
+++ b/crates/circuit/src/operations.rs
@@ -24,6 +24,9 @@ use pyo3::prelude::*;
 use pyo3::{intern, IntoPy, Python};
 use smallvec::smallvec;
 
+const PI2: f64 = PI / 2.0;
+const PI4: f64 = PI / 4.0;
+
 /// Valid types for an operation field in a CircuitInstruction
 ///
 /// These are basically the types allowed in a QuantumCircuit
@@ -194,13 +197,21 @@ pub enum StandardGate {
     HGate = 15,
     PhaseGate = 16,
     UGate = 17,
+    SGate = 18,
+    SdgGate = 19,
+    TGate = 20,
+    TdgGate = 21,
+    SXdgGate = 22,
+    ISwapGate = 23,
 }
 
-static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] =
-    [1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1];
+static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [
+    1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+];
 
-static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] =
-    [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3];
+static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [
+    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0,
+];
 
 static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
     "z",
@@ -221,6 +232,12 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
     "h",
     "p",
     "u",
+    "s",
+    "sdg",
+    "t",
+    "tdg",
+    "sxdg",
+    "iswap",
 ];
 
 #[pymethods]
@@ -269,7 +286,8 @@ impl StandardGate {
 //
 // Remove this when std::mem::variant_count() is stabilized (see
 // https://github.com/rust-lang/rust/issues/73662 )
-pub const STANDARD_GATE_SIZE: usize = 18;
+
+pub const STANDARD_GATE_SIZE: usize = 24;
 
 impl Operation for StandardGate {
     fn name(&self) -> &str {
@@ -350,6 +368,10 @@ impl Operation for StandardGate {
                 [] => Some(aview2(&gate_matrix::SX_GATE).to_owned()),
                 _ => None,
             },
+            Self::SXdgGate => match params {
+                [] => Some(aview2(&gate_matrix::SXDG_GATE).to_owned()),
+                _ => None,
+            },
             Self::GlobalPhaseGate => match params {
                 [Param::Float(theta)] => {
                     Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned())
@@ -374,6 +396,26 @@ impl Operation for StandardGate {
                 }
                 _ => None,
             },
+            Self::SGate => match params {
+                [] => Some(aview2(&gate_matrix::S_GATE).to_owned()),
+                _ => None,
+            },
+            Self::SdgGate => match params {
+                [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()),
+                _ => None,
+            },
+            Self::TGate => match params {
+                [] => Some(aview2(&gate_matrix::T_GATE).to_owned()),
+                _ => None,
+            },
+            Self::TdgGate => match params {
+                [] => Some(aview2(&gate_matrix::TDG_GATE).to_owned()),
+                _ => None,
+            },
+            Self::ISwapGate => match params {
+                [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()),
+                _ => None,
+            },
         }
     }
 
@@ -401,11 +443,7 @@ impl Operation for StandardGate {
                         1,
                         [(
                             Self::UGate,
-                            smallvec![
-                                Param::Float(PI),
-                                Param::Float(PI / 2.),
-                                Param::Float(PI / 2.),
-                            ],
+                            smallvec![Param::Float(PI), Param::Float(PI2), Param::Float(PI2),],
                             smallvec![Qubit(0)],
                         )],
                         FLOAT_ZERO,
@@ -445,9 +483,56 @@ impl Operation for StandardGate {
                     .expect("Unexpected Qiskit python bug"),
                 )
             }),
-            Self::CYGate => todo!("Add when we have S and S dagger"),
+            Self::CYGate => Python::with_gil(|py| -> Option {
+                let q1 = smallvec![Qubit(1)];
+                let q0_1 = smallvec![Qubit(0), Qubit(1)];
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        2,
+                        [
+                            (Self::SdgGate, smallvec![], q1.clone()),
+                            (Self::CXGate, smallvec![], q0_1),
+                            (Self::SGate, smallvec![], q1),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
             Self::CXGate => None,
-            Self::CCXGate => todo!("Add when we have T and TDagger"),
+            Self::CCXGate => Python::with_gil(|py| -> Option {
+                let q1 = smallvec![Qubit(1)];
+                let q2 = smallvec![Qubit(2)];
+                let q0_1 = smallvec![Qubit(0), Qubit(1)];
+                let q0_2 = smallvec![Qubit(0), Qubit(2)];
+                let q1_2 = smallvec![Qubit(1), Qubit(2)];
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        3,
+                        [
+                            (Self::HGate, smallvec![], q2.clone()),
+                            (Self::CXGate, smallvec![], q1_2.clone()),
+                            (Self::TdgGate, smallvec![], q2.clone()),
+                            (Self::CXGate, smallvec![], q0_2.clone()),
+                            (Self::TGate, smallvec![], q2.clone()),
+                            (Self::CXGate, smallvec![], q1_2),
+                            (Self::TdgGate, smallvec![], q2.clone()),
+                            (Self::CXGate, smallvec![], q0_2),
+                            (Self::TGate, smallvec![], q1.clone()),
+                            (Self::TGate, smallvec![], q2.clone()),
+                            (Self::HGate, smallvec![], q2),
+                            (Self::CXGate, smallvec![], q0_1.clone()),
+                            (Self::TGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::TdgGate, smallvec![], q1),
+                            (Self::CXGate, smallvec![], q0_1),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
             Self::RXGate => todo!("Add when we have R"),
             Self::RYGate => todo!("Add when we have R"),
             Self::RZGate => Python::with_gil(|py| -> Option {
@@ -501,7 +586,36 @@ impl Operation for StandardGate {
                     .expect("Unexpected Qiskit python bug"),
                 )
             }),
-            Self::SXGate => todo!("Add when we have S dagger"),
+            Self::SXGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [
+                            (Self::SdgGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::HGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::SdgGate, smallvec![], smallvec![Qubit(0)]),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::SXdgGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [
+                            (Self::SGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::HGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::SGate, smallvec![], smallvec![Qubit(0)]),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
             Self::GlobalPhaseGate => Python::with_gil(|py| -> Option {
                 Some(
                     CircuitData::from_standard_gates(py, 0, [], params[0].clone())
@@ -516,7 +630,7 @@ impl Operation for StandardGate {
                         1,
                         [(
                             Self::UGate,
-                            smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)],
+                            smallvec![Param::Float(PI2), Param::Float(0.), Param::Float(PI)],
                             smallvec![Qubit(0)],
                         )],
                         FLOAT_ZERO,
@@ -540,6 +654,84 @@ impl Operation for StandardGate {
                 )
             }),
             Self::UGate => None,
+            Self::SGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::PhaseGate,
+                            smallvec![Param::Float(PI2)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::SdgGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::PhaseGate,
+                            smallvec![Param::Float(-PI2)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::TGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::PhaseGate,
+                            smallvec![Param::Float(PI4)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::TdgGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        1,
+                        [(
+                            Self::PhaseGate,
+                            smallvec![Param::Float(-PI4)],
+                            smallvec![Qubit(0)],
+                        )],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
+            Self::ISwapGate => Python::with_gil(|py| -> Option {
+                Some(
+                    CircuitData::from_standard_gates(
+                        py,
+                        2,
+                        [
+                            (Self::SGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::SGate, smallvec![], smallvec![Qubit(1)]),
+                            (Self::HGate, smallvec![], smallvec![Qubit(0)]),
+                            (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
+                            (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]),
+                            (Self::HGate, smallvec![], smallvec![Qubit(1)]),
+                        ],
+                        FLOAT_ZERO,
+                    )
+                    .expect("Unexpected Qiskit python bug"),
+                )
+            }),
         }
     }
 
diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py
index 50d3a6bb3473..8074990a3840 100644
--- a/qiskit/circuit/library/standard_gates/iswap.py
+++ b/qiskit/circuit/library/standard_gates/iswap.py
@@ -19,6 +19,7 @@
 from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 from .xx_plus_yy import XXPlusYYGate
 
@@ -85,6 +86,8 @@ class iSwapGate(SingletonGate):
             \end{pmatrix}
     """
 
+    _standard_gate = StandardGate.ISwapGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new iSwap gate."""
         super().__init__("iswap", 2, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py
index 6fde1c6544e5..f62d16a10d40 100644
--- a/qiskit/circuit/library/standard_gates/s.py
+++ b/qiskit/circuit/library/standard_gates/s.py
@@ -20,6 +20,7 @@
 from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 
 _S_ARRAY = numpy.array([[1, 0], [0, 1j]])
@@ -57,6 +58,8 @@ class SGate(SingletonGate):
     Equivalent to a :math:`\pi/2` radian rotation about the Z axis.
     """
 
+    _standard_gate = StandardGate.SGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new S gate."""
         super().__init__("s", 1, [], label=label, duration=duration, unit=unit)
@@ -134,6 +137,8 @@ class SdgGate(SingletonGate):
     Equivalent to a :math:`-\pi/2` radian rotation about the Z axis.
     """
 
+    _standard_gate = StandardGate.SdgGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new Sdg gate."""
         super().__init__("sdg", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py
index 93ca85da0198..72e4a8f9b5bf 100644
--- a/qiskit/circuit/library/standard_gates/sx.py
+++ b/qiskit/circuit/library/standard_gates/sx.py
@@ -167,6 +167,8 @@ class SXdgGate(SingletonGate):
                     = e^{-i \pi/4} \sqrt{X}^{\dagger}
     """
 
+    _standard_gate = StandardGate.SXdgGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new SXdg gate."""
         super().__init__("sxdg", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py
index 87a38d9d44c1..e4301168ac53 100644
--- a/qiskit/circuit/library/standard_gates/t.py
+++ b/qiskit/circuit/library/standard_gates/t.py
@@ -21,6 +21,7 @@
 from qiskit.circuit.library.standard_gates.p import PhaseGate
 from qiskit.circuit.quantumregister import QuantumRegister
 from qiskit.circuit._utils import with_gate_array
+from qiskit._accelerate.circuit import StandardGate
 
 
 @with_gate_array([[1, 0], [0, (1 + 1j) / math.sqrt(2)]])
@@ -55,6 +56,8 @@ class TGate(SingletonGate):
     Equivalent to a :math:`\pi/4` radian rotation about the Z axis.
     """
 
+    _standard_gate = StandardGate.TGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new T gate."""
         super().__init__("t", 1, [], label=label, duration=duration, unit=unit)
@@ -130,6 +133,8 @@ class TdgGate(SingletonGate):
     Equivalent to a :math:`-\pi/4` radian rotation about the Z axis.
     """
 
+    _standard_gate = StandardGate.TdgGate
+
     def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
         """Create new Tdg gate."""
         super().__init__("tdg", 1, [], label=label, duration=duration, unit=unit)
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 238a2682522a..e3fbf40da683 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -4979,9 +4979,7 @@ def s(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.s import SGate
-
-        return self.append(SGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.SGate, [], [qubit], cargs=None)
 
     def sdg(self, qubit: QubitSpecifier) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.SdgGate`.
@@ -4994,9 +4992,7 @@ def sdg(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.s import SdgGate
-
-        return self.append(SdgGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.SdgGate, [], [qubit], cargs=None)
 
     def cs(
         self,
@@ -5089,9 +5085,7 @@ def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSe
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.iswap import iSwapGate
-
-        return self.append(iSwapGate(), [qubit1, qubit2], [], copy=False)
+        return self._append_standard_gate(StandardGate.ISwapGate, [], [qubit1, qubit2], cargs=None)
 
     def cswap(
         self,
@@ -5150,9 +5144,7 @@ def sxdg(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.sx import SXdgGate
-
-        return self.append(SXdgGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.SXdgGate, None, qargs=[qubit])
 
     def csx(
         self,
@@ -5196,9 +5188,7 @@ def t(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.t import TGate
-
-        return self.append(TGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.TGate, [], [qubit], cargs=None)
 
     def tdg(self, qubit: QubitSpecifier) -> InstructionSet:
         """Apply :class:`~qiskit.circuit.library.TdgGate`.
@@ -5211,9 +5201,7 @@ def tdg(self, qubit: QubitSpecifier) -> InstructionSet:
         Returns:
             A handle to the instructions created.
         """
-        from .library.standard_gates.t import TdgGate
-
-        return self.append(TdgGate(), [qubit], [], copy=False)
+        return self._append_standard_gate(StandardGate.TdgGate, [], [qubit], cargs=None)
 
     def u(
         self,
diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py
index 06d4ed86a60a..bb09ae4caf3f 100644
--- a/test/python/circuit/test_rust_equivalence.py
+++ b/test/python/circuit/test_rust_equivalence.py
@@ -21,7 +21,7 @@
 from qiskit.circuit import QuantumCircuit
 from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
 
-SKIP_LIST = {"cy", "ccx", "rx", "ry", "ecr", "sx"}
+SKIP_LIST = {"rx", "ry", "ecr"}
 CUSTOM_MAPPING = {"x", "rz"}
 
 

From 53667d167e2de2f841d3b781877427f0b459289b Mon Sep 17 00:00:00 2001
From: Joe Schulte 
Date: Wed, 19 Jun 2024 03:05:56 -0400
Subject: [PATCH 155/179] Remove consider-using-f-string lint rule and updates
 (#12423)

* remove consider-using-f-string lint rule and updates

* reverting a latex update

* f-string update based on review

* Update qiskit/circuit/library/hamiltonian_gate.py

Co-authored-by: Matthew Treinish 

* Update qiskit/circuit/tools/pi_check.py

Co-authored-by: Matthew Treinish 

* Update qiskit/circuit/tools/pi_check.py

Co-authored-by: Matthew Treinish 

* updates after merge

* Update qiskit/providers/models/backendproperties.py

Co-authored-by: Julien Gacon 

* Update qiskit/synthesis/linear/cnot_synth.py

Co-authored-by: Julien Gacon 

* updates from PR

* latex fixes

---------

Co-authored-by: Matthew Treinish 
Co-authored-by: Julien Gacon 
---
 pyproject.toml                                |  1 -
 qiskit/assembler/assemble_circuits.py         |  4 +-
 qiskit/assembler/assemble_schedules.py        | 17 ++----
 qiskit/assembler/disassemble.py               |  2 +-
 .../classicalfunction/boolean_expression.py   |  2 +-
 .../classical_function_visitor.py             | 10 ++--
 qiskit/circuit/classicalfunction/utils.py     |  2 +-
 qiskit/circuit/classicalregister.py           |  2 +-
 qiskit/circuit/delay.py                       |  2 +-
 qiskit/circuit/duration.py                    |  4 +-
 qiskit/circuit/equivalence.py                 | 12 ++--
 qiskit/circuit/gate.py                        |  4 +-
 qiskit/circuit/instruction.py                 |  9 +--
 .../arithmetic/linear_pauli_rotations.py      |  2 +-
 .../library/arithmetic/piecewise_chebyshev.py |  2 +-
 .../piecewise_linear_pauli_rotations.py       |  2 +-
 .../piecewise_polynomial_pauli_rotations.py   |  2 +-
 .../arithmetic/polynomial_pauli_rotations.py  |  2 +-
 .../data_preparation/state_preparation.py     | 14 ++---
 qiskit/circuit/library/graph_state.py         |  2 +-
 qiskit/circuit/library/hamiltonian_gate.py    |  3 +-
 .../circuit/library/hidden_linear_function.py |  2 +-
 qiskit/circuit/library/n_local/n_local.py     |  5 +-
 qiskit/circuit/library/n_local/qaoa_ansatz.py | 14 ++---
 qiskit/circuit/library/overlap.py             |  6 +-
 qiskit/circuit/library/standard_gates/u3.py   |  2 +-
 qiskit/circuit/parameter.py                   |  4 +-
 qiskit/circuit/parameterexpression.py         | 18 +++---
 qiskit/circuit/quantumcircuit.py              | 13 +++--
 qiskit/circuit/quantumregister.py             |  2 +-
 qiskit/circuit/register.py                    | 12 ++--
 qiskit/circuit/tools/pi_check.py              |  8 +--
 qiskit/compiler/assembler.py                  | 24 ++++----
 qiskit/compiler/scheduler.py                  |  2 +-
 qiskit/compiler/transpiler.py                 |  4 +-
 qiskit/converters/circuit_to_gate.py          | 12 ++--
 qiskit/converters/circuit_to_instruction.py   |  6 +-
 qiskit/dagcircuit/dagcircuit.py               | 57 +++++++++----------
 qiskit/dagcircuit/dagdependency.py            |  8 +--
 qiskit/dagcircuit/dagdependency_v2.py         |  8 +--
 qiskit/dagcircuit/dagdepnode.py               |  2 +-
 qiskit/passmanager/flow_controllers.py        |  2 +-
 qiskit/passmanager/passmanager.py             |  2 +-
 qiskit/providers/backend.py                   |  8 +--
 .../basic_provider/basic_provider_tools.py    |  2 +-
 .../basic_provider/basic_simulator.py         |  4 +-
 .../providers/fake_provider/fake_backend.py   |  4 +-
 .../fake_provider/generic_backend_v2.py       |  4 +-
 qiskit/providers/models/backendproperties.py  |  4 +-
 qiskit/providers/models/pulsedefaults.py      |  7 +--
 qiskit/providers/options.py                   |  4 +-
 qiskit/pulse/configuration.py                 | 20 +++----
 qiskit/pulse/instruction_schedule_map.py      |  6 +-
 qiskit/pulse/instructions/acquire.py          | 15 +++--
 qiskit/pulse/instructions/instruction.py      |  5 +-
 qiskit/pulse/library/samplers/decorators.py   | 14 ++---
 qiskit/pulse/library/symbolic_pulses.py       |  7 +--
 qiskit/pulse/library/waveform.py              |  7 +--
 qiskit/pulse/macros.py                        |  8 +--
 qiskit/pulse/parser.py                        | 14 ++---
 qiskit/pulse/schedule.py                      | 26 +++------
 qiskit/pulse/transforms/alignments.py         |  4 +-
 qiskit/pulse/utils.py                         |  3 +-
 qiskit/qasm2/export.py                        | 12 ++--
 qiskit/qobj/converters/pulse_instruction.py   |  6 +-
 qiskit/qobj/pulse_qobj.py                     | 27 ++++-----
 qiskit/qobj/qasm_qobj.py                      | 35 +++++-------
 qiskit/qpy/binary_io/circuits.py              | 10 ++--
 qiskit/qpy/binary_io/value.py                 |  6 +-
 qiskit/qpy/interface.py                       |  5 +-
 .../operators/channel/quantum_channel.py      |  9 +--
 .../quantum_info/operators/channel/superop.py |  4 +-
 .../operators/dihedral/dihedral_circuits.py   |  4 +-
 qiskit/quantum_info/operators/measures.py     |  2 +-
 qiskit/quantum_info/operators/op_shape.py     | 32 ++++-------
 qiskit/quantum_info/operators/operator.py     | 15 ++---
 qiskit/quantum_info/operators/predicates.py   |  1 +
 .../operators/symplectic/base_pauli.py        | 18 +++---
 .../operators/symplectic/pauli.py             |  6 +-
 .../operators/symplectic/pauli_list.py        | 17 +++---
 .../operators/symplectic/sparse_pauli_op.py   | 10 ++--
 qiskit/quantum_info/states/densitymatrix.py   | 13 ++---
 qiskit/quantum_info/states/statevector.py     | 12 ++--
 qiskit/result/counts.py                       |  2 +-
 .../correlated_readout_mitigator.py           |  4 +-
 .../mitigation/local_readout_mitigator.py     |  4 +-
 qiskit/result/mitigation/utils.py             |  4 +-
 qiskit/result/models.py                       | 21 +++----
 qiskit/result/result.py                       | 25 +++-----
 qiskit/scheduler/lowering.py                  |  4 +-
 qiskit/synthesis/linear/cnot_synth.py         |  3 +-
 .../two_qubit/two_qubit_decompose.py          |  2 +-
 qiskit/transpiler/coupling.py                 |  6 +-
 qiskit/transpiler/layout.py                   |  6 +-
 .../passes/basis/basis_translator.py          |  4 +-
 .../passes/basis/unroll_3q_or_more.py         |  2 +-
 .../passes/basis/unroll_custom_definitions.py |  6 +-
 .../passes/calibration/rzx_builder.py         |  6 +-
 .../optimization/inverse_cancellation.py      |  4 +-
 .../passes/optimization/optimize_1q_gates.py  |  2 +-
 .../transpiler/passes/routing/sabre_swap.py   |  2 +-
 .../passes/routing/stochastic_swap.py         |  4 +-
 .../passes/synthesis/high_level_synthesis.py  |  4 +-
 qiskit/transpiler/passes/utils/check_map.py   |  6 +-
 qiskit/transpiler/passes/utils/error.py       |  4 +-
 qiskit/transpiler/passes/utils/fixed_point.py |  6 +-
 .../transpiler/preset_passmanagers/common.py  |  4 +-
 qiskit/transpiler/target.py                   |  4 +-
 qiskit/user_config.py                         | 12 ++--
 .../circuit/circuit_visualization.py          |  4 +-
 qiskit/visualization/circuit/latex.py         | 33 +++++------
 qiskit/visualization/circuit/matplotlib.py    |  2 +-
 qiskit/visualization/circuit/text.py          | 13 +++--
 qiskit/visualization/dag_visualization.py     |  4 +-
 qiskit/visualization/pulse_v2/core.py         |  2 +-
 .../pulse_v2/generators/frame.py              |  7 +--
 .../pulse_v2/generators/waveform.py           | 14 ++---
 qiskit/visualization/pulse_v2/layouts.py      |  6 +-
 .../pulse_v2/plotters/matplotlib.py           |  3 +-
 qiskit/visualization/state_visualization.py   | 11 ++--
 .../timeline/plotters/matplotlib.py           |  3 +-
 test/benchmarks/circuit_construction.py       |  2 +-
 test/python/circuit/test_circuit_qasm.py      |  2 +-
 test/python/circuit/test_circuit_registers.py |  2 +-
 test/python/circuit/test_instructions.py      | 10 ++--
 test/python/circuit/test_parameters.py        | 15 +++--
 test/python/dagcircuit/test_dagcircuit.py     |  8 +--
 test/python/providers/test_fake_backends.py   |  7 +--
 .../operators/symplectic/test_clifford.py     | 10 ++--
 .../quantum_info/states/test_densitymatrix.py |  4 +-
 test/python/result/test_mitigators.py         | 54 +++++++-----------
 .../aqc/fast_gradient/test_layer1q.py         |  9 ++-
 .../aqc/fast_gradient/test_layer2q.py         | 10 ++--
 .../synthesis/test_permutation_synthesis.py   |  8 +--
 test/python/test_user_config.py               |  2 +-
 test/python/transpiler/test_pass_scheduler.py |  2 +-
 .../visualization/timeline/test_generators.py | 11 ++--
 .../randomized/test_transpiler_equivalence.py |  7 +--
 test/utils/base.py                            | 12 ++--
 test/visual/results.py                        | 31 +++++-----
 tools/build_standard_commutations.py          |  4 +-
 tools/find_stray_release_notes.py             |  2 +-
 tools/verify_headers.py                       | 12 ++--
 143 files changed, 516 insertions(+), 684 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 0740861c98c6..2f62557aa15b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -218,7 +218,6 @@ disable = [
 # TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so,
 #  remove from here and fix the issues. Else, move it above this section and add a comment
 #  with the rationale
-    "consider-using-f-string",
     "no-member",  # for dynamically created members
     "not-context-manager",
     "unnecessary-lambda-assignment",  # do not want to implement
diff --git a/qiskit/assembler/assemble_circuits.py b/qiskit/assembler/assemble_circuits.py
index b27fe47a02e6..a3d9b6bbb549 100644
--- a/qiskit/assembler/assemble_circuits.py
+++ b/qiskit/assembler/assemble_circuits.py
@@ -153,9 +153,9 @@ def _assemble_circuit(
             conditional_reg_idx = memory_slots + max_conditional_idx
             conversion_bfunc = QasmQobjInstruction(
                 name="bfunc",
-                mask="0x%X" % mask,
+                mask="0x%X" % mask,  # pylint: disable=consider-using-f-string
                 relation="==",
-                val="0x%X" % val,
+                val="0x%X" % val,  # pylint: disable=consider-using-f-string
                 register=conditional_reg_idx,
             )
             instructions.append(conversion_bfunc)
diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py
index c60c28ff9a50..2d5ebefa2fd1 100644
--- a/qiskit/assembler/assemble_schedules.py
+++ b/qiskit/assembler/assemble_schedules.py
@@ -152,7 +152,7 @@ def _assemble_experiments(
         # TODO: add other experimental header items (see circuit assembler)
         qobj_experiment_header = qobj.QobjExperimentHeader(
             memory_slots=max_memory_slot + 1,  # Memory slots are 0 indexed
-            name=sched.name or "Experiment-%d" % idx,
+            name=sched.name or f"Experiment-{idx}",
             metadata=metadata,
         )
 
@@ -306,18 +306,11 @@ def _validate_meas_map(
                 common_next = next_inst_qubits.intersection(meas_set)
                 if common_instr_qubits and common_next:
                     raise QiskitError(
-                        "Qubits {} and {} are in the same measurement grouping: {}. "
+                        f"Qubits {common_instr_qubits} and {common_next} are in the same measurement "
+                        f"grouping: {meas_map}. "
                         "They must either be acquired at the same time, or disjointly"
-                        ". Instead, they were acquired at times: {}-{} and "
-                        "{}-{}".format(
-                            common_instr_qubits,
-                            common_next,
-                            meas_map,
-                            inst[0][0],
-                            inst_end_time,
-                            next_inst_time,
-                            next_inst_time + next_inst[0][1],
-                        )
+                        f". Instead, they were acquired at times: {inst[0][0]}-{inst_end_time} and "
+                        f"{next_inst_time}-{next_inst_time + next_inst[0][1]}"
                     )
 
 
diff --git a/qiskit/assembler/disassemble.py b/qiskit/assembler/disassemble.py
index c94b108c4b25..127bbd35eb26 100644
--- a/qiskit/assembler/disassemble.py
+++ b/qiskit/assembler/disassemble.py
@@ -109,7 +109,7 @@ def _qobj_to_circuit_cals(qobj, pulse_lib):
         config = (tuple(gate["qubits"]), tuple(gate["params"]))
         cal = {
             config: pulse.Schedule(
-                name="{} {} {}".format(gate["name"], str(gate["params"]), str(gate["qubits"]))
+                name=f"{gate['name']} {str(gate['params'])} {str(gate['qubits'])}"
             )
         }
         for instruction in gate["instructions"]:
diff --git a/qiskit/circuit/classicalfunction/boolean_expression.py b/qiskit/circuit/classicalfunction/boolean_expression.py
index 0f4a53494af4..e517f36db02b 100644
--- a/qiskit/circuit/classicalfunction/boolean_expression.py
+++ b/qiskit/circuit/classicalfunction/boolean_expression.py
@@ -116,7 +116,7 @@ def from_dimacs_file(cls, filename: str):
 
         expr_obj = cls.__new__(cls)
         if not isfile(filename):
-            raise FileNotFoundError("The file %s does not exists." % filename)
+            raise FileNotFoundError(f"The file {filename} does not exists.")
         expr_obj._tweedledum_bool_expression = BoolFunction.from_dimacs_file(filename)
 
         num_qubits = (
diff --git a/qiskit/circuit/classicalfunction/classical_function_visitor.py b/qiskit/circuit/classicalfunction/classical_function_visitor.py
index dfe8b956b092..be89e8ee7f81 100644
--- a/qiskit/circuit/classicalfunction/classical_function_visitor.py
+++ b/qiskit/circuit/classicalfunction/classical_function_visitor.py
@@ -83,7 +83,7 @@ def bit_binop(self, op, values):
         """Uses ClassicalFunctionVisitor.bitops to extend self._network"""
         bitop = ClassicalFunctionVisitor.bitops.get(type(op))
         if not bitop:
-            raise ClassicalFunctionParseError("Unknown binop.op %s" % op)
+            raise ClassicalFunctionParseError(f"Unknown binop.op {op}")
         binop = getattr(self._network, bitop)
 
         left_type, left_signal = values[0]
@@ -112,19 +112,19 @@ def visit_UnaryOp(self, node):
         operand_type, operand_signal = self.visit(node.operand)
         if operand_type != "Int1":
             raise ClassicalFunctionCompilerTypeError(
-                "UntaryOp.op %s only support operation on Int1s for now" % node.op
+                f"UntaryOp.op {node.op} only support operation on Int1s for now"
             )
         bitop = ClassicalFunctionVisitor.bitops.get(type(node.op))
         if not bitop:
             raise ClassicalFunctionCompilerTypeError(
-                "UntaryOp.op %s does not operate with Int1 type " % node.op
+                f"UntaryOp.op {node.op} does not operate with Int1 type "
             )
         return "Int1", getattr(self._network, bitop)(operand_signal)
 
     def visit_Name(self, node):
         """Reduce variable names."""
         if node.id not in self.scopes[-1]:
-            raise ClassicalFunctionParseError("out of scope: %s" % node.id)
+            raise ClassicalFunctionParseError(f"out of scope: {node.id}")
         return self.scopes[-1][node.id]
 
     def generic_visit(self, node):
@@ -143,7 +143,7 @@ def generic_visit(self, node):
             ),
         ):
             return super().generic_visit(node)
-        raise ClassicalFunctionParseError("Unknown node: %s" % type(node))
+        raise ClassicalFunctionParseError(f"Unknown node: {type(node)}")
 
     def extend_scope(self, args_node: _ast.arguments) -> None:
         """Add the arguments to the scope"""
diff --git a/qiskit/circuit/classicalfunction/utils.py b/qiskit/circuit/classicalfunction/utils.py
index 237a8b838530..75dcd3e20a7a 100644
--- a/qiskit/circuit/classicalfunction/utils.py
+++ b/qiskit/circuit/classicalfunction/utils.py
@@ -47,7 +47,7 @@ def _convert_tweedledum_operator(op):
         if op.kind() == "py_operator":
             return op.py_op()
         else:
-            raise RuntimeError("Unrecognized operator: %s" % op.kind())
+            raise RuntimeError(f"Unrecognized operator: {op.kind()}")
 
     # TODO: need to deal with cbits too!
     if op.num_controls() > 0:
diff --git a/qiskit/circuit/classicalregister.py b/qiskit/circuit/classicalregister.py
index 7a21e6b2fa58..802d8c602e2c 100644
--- a/qiskit/circuit/classicalregister.py
+++ b/qiskit/circuit/classicalregister.py
@@ -43,7 +43,7 @@ def __init__(self, register=None, index=None):
             super().__init__(register, index)
         else:
             raise CircuitError(
-                "Clbit needs a ClassicalRegister and %s was provided" % type(register).__name__
+                f"Clbit needs a ClassicalRegister and {type(register).__name__} was provided"
             )
 
 
diff --git a/qiskit/circuit/delay.py b/qiskit/circuit/delay.py
index a333125a5a2b..25e7a6f3356c 100644
--- a/qiskit/circuit/delay.py
+++ b/qiskit/circuit/delay.py
@@ -32,7 +32,7 @@ def __init__(self, duration, unit="dt"):
             unit: the unit of the duration.  Must be ``"dt"`` or an SI-prefixed seconds unit.
         """
         if unit not in {"s", "ms", "us", "ns", "ps", "dt"}:
-            raise CircuitError("Unknown unit %s is specified." % unit)
+            raise CircuitError(f"Unknown unit {unit} is specified.")
 
         super().__init__("delay", 1, 0, params=[duration], unit=unit)
 
diff --git a/qiskit/circuit/duration.py b/qiskit/circuit/duration.py
index 6acb230baadd..fdf6e99e6117 100644
--- a/qiskit/circuit/duration.py
+++ b/qiskit/circuit/duration.py
@@ -35,8 +35,8 @@ def duration_in_dt(duration_in_sec: float, dt_in_sec: float) -> int:
     rounding_error = abs(duration_in_sec - res * dt_in_sec)
     if rounding_error > 1e-15:
         warnings.warn(
-            "Duration is rounded to %d [dt] = %e [s] from %e [s]"
-            % (res, res * dt_in_sec, duration_in_sec),
+            f"Duration is rounded to {res:d} [dt] = {res * dt_in_sec:e} [s] "
+            f"from {duration_in_sec:e} [s]",
             UserWarning,
         )
     return res
diff --git a/qiskit/circuit/equivalence.py b/qiskit/circuit/equivalence.py
index 45921c3f2293..17912517d244 100644
--- a/qiskit/circuit/equivalence.py
+++ b/qiskit/circuit/equivalence.py
@@ -249,7 +249,7 @@ def _build_basis_graph(self):
                     )
                     node_map[decomp_basis] = decomp_basis_node
 
-                label = "{}\n{}".format(str(params), str(decomp) if num_qubits <= 5 else "...")
+                label = f"{str(params)}\n{str(decomp) if num_qubits <= 5 else '...'}"
                 graph.add_edge(
                     node_map[basis],
                     node_map[decomp_basis],
@@ -273,8 +273,8 @@ def _raise_if_param_mismatch(gate_params, circuit_parameters):
     if set(gate_parameters) != circuit_parameters:
         raise CircuitError(
             "Cannot add equivalence between circuit and gate "
-            "of different parameters. Gate params: {}. "
-            "Circuit params: {}.".format(gate_parameters, circuit_parameters)
+            f"of different parameters. Gate params: {gate_parameters}. "
+            f"Circuit params: {circuit_parameters}."
         )
 
 
@@ -282,10 +282,8 @@ def _raise_if_shape_mismatch(gate, circuit):
     if gate.num_qubits != circuit.num_qubits or gate.num_clbits != circuit.num_clbits:
         raise CircuitError(
             "Cannot add equivalence between circuit and gate "
-            "of different shapes. Gate: {} qubits and {} clbits. "
-            "Circuit: {} qubits and {} clbits.".format(
-                gate.num_qubits, gate.num_clbits, circuit.num_qubits, circuit.num_clbits
-            )
+            f"of different shapes. Gate: {gate.num_qubits} qubits and {gate.num_clbits} clbits. "
+            f"Circuit: {circuit.num_qubits} qubits and {circuit.num_clbits} clbits."
         )
 
 
diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py
index 132526775860..d2c88f40bdb6 100644
--- a/qiskit/circuit/gate.py
+++ b/qiskit/circuit/gate.py
@@ -177,7 +177,7 @@ def _broadcast_3_or_more_args(qargs: list) -> Iterator[tuple[list, list]]:
             for arg in zip(*qargs):
                 yield list(arg), []
         else:
-            raise CircuitError("Not sure how to combine these qubit arguments:\n %s\n" % qargs)
+            raise CircuitError(f"Not sure how to combine these qubit arguments:\n {qargs}\n")
 
     def broadcast_arguments(self, qargs: list, cargs: list) -> Iterable[tuple[list, list]]:
         """Validation and handling of the arguments and its relationship.
@@ -236,7 +236,7 @@ def broadcast_arguments(self, qargs: list, cargs: list) -> Iterable[tuple[list,
         elif len(qargs) >= 3:
             return Gate._broadcast_3_or_more_args(qargs)
         else:
-            raise CircuitError("This gate cannot handle %i arguments" % len(qargs))
+            raise CircuitError(f"This gate cannot handle {len(qargs)} arguments")
 
     def validate_parameter(self, parameter):
         """Gate parameters should be int, float, or ParameterExpression"""
diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py
index 44155783d409..f53c5b9e9b3c 100644
--- a/qiskit/circuit/instruction.py
+++ b/qiskit/circuit/instruction.py
@@ -81,7 +81,7 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt
             raise CircuitError("num_qubits and num_clbits must be integer.")
         if num_qubits < 0 or num_clbits < 0:
             raise CircuitError(
-                "bad instruction dimensions: %d qubits, %d clbits." % num_qubits, num_clbits
+                f"bad instruction dimensions: {num_qubits} qubits, {num_clbits} clbits."
             )
         self._name = name
         self._num_qubits = num_qubits
@@ -222,8 +222,9 @@ def __repr__(self) -> str:
             str: A representation of the Instruction instance with the name,
                  number of qubits, classical bits and params( if any )
         """
-        return "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format(
-            self.name, self.num_qubits, self.num_clbits, self.params
+        return (
+            f"Instruction(name='{self.name}', num_qubits={self.num_qubits}, "
+            f"num_clbits={self.num_clbits}, params={self.params})"
         )
 
     def soft_compare(self, other: "Instruction") -> bool:
@@ -456,7 +457,7 @@ def inverse(self, annotated: bool = False):
             return AnnotatedOperation(self, InverseModifier())
 
         if self.definition is None:
-            raise CircuitError("inverse() not implemented for %s." % self.name)
+            raise CircuitError(f"inverse() not implemented for {self.name}.")
 
         from qiskit.circuit import Gate  # pylint: disable=cyclic-import
 
diff --git a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py
index a40767154bc9..bc80ef778616 100644
--- a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py
+++ b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py
@@ -153,7 +153,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             if raise_on_failure:
                 raise CircuitError(
                     "Not enough qubits in the circuit, need at least "
-                    "{}.".format(self.num_state_qubits + 1)
+                    f"{self.num_state_qubits + 1}."
                 )
 
         return valid
diff --git a/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py
index a27c57ef28fa..cc34d3631f59 100644
--- a/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py
+++ b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py
@@ -122,7 +122,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             if raise_on_failure:
                 raise CircuitError(
                     "Not enough qubits in the circuit, need at least "
-                    "{}.".format(self.num_state_qubits + 1)
+                    f"{self.num_state_qubits + 1}."
                 )
 
         return valid
diff --git a/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py
index 509433af5574..3d84e64ccb1b 100644
--- a/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py
+++ b/qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py
@@ -202,7 +202,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             if raise_on_failure:
                 raise CircuitError(
                     "Not enough qubits in the circuit, need at least "
-                    "{}.".format(self.num_state_qubits + 1)
+                    f"{self.num_state_qubits + 1}."
                 )
 
         if len(self.breakpoints) != len(self.slopes) or len(self.breakpoints) != len(self.offsets):
diff --git a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
index 7e79ed04da12..741b920e368d 100644
--- a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
+++ b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py
@@ -237,7 +237,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             if raise_on_failure:
                 raise CircuitError(
                     "Not enough qubits in the circuit, need at least "
-                    "{}.".format(self.num_state_qubits + 1)
+                    f"{self.num_state_qubits + 1}."
                 )
 
         if len(self.breakpoints) != len(self.coeffs) + 1:
diff --git a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py
index 13fb82298819..4f04a04dd522 100644
--- a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py
+++ b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py
@@ -248,7 +248,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             if raise_on_failure:
                 raise CircuitError(
                     "Not enough qubits in the circuit, need at least "
-                    "{}.".format(self.num_state_qubits + 1)
+                    f"{self.num_state_qubits + 1}."
                 )
 
         return valid
diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py
index 43e80ead8836..1d9ad7f7b082 100644
--- a/qiskit/circuit/library/data_preparation/state_preparation.py
+++ b/qiskit/circuit/library/data_preparation/state_preparation.py
@@ -154,8 +154,8 @@ def _define_from_int(self):
         # Raise if number of bits is greater than num_qubits
         if len(intstr) > self.num_qubits:
             raise QiskitError(
-                "StatePreparation integer has %s bits, but this exceeds the"
-                " number of qubits in the circuit, %s." % (len(intstr), self.num_qubits)
+                f"StatePreparation integer has {len(intstr)} bits, but this exceeds the"
+                f" number of qubits in the circuit, {self.num_qubits}."
             )
 
         for qubit, bit in enumerate(intstr):
@@ -212,9 +212,9 @@ def broadcast_arguments(self, qargs, cargs):
 
         if self.num_qubits != len(flat_qargs):
             raise QiskitError(
-                "StatePreparation parameter vector has %d elements, therefore expects %s "
-                "qubits. However, %s were provided."
-                % (2**self.num_qubits, self.num_qubits, len(flat_qargs))
+                f"StatePreparation parameter vector has {2**self.num_qubits}"
+                f" elements, therefore expects {self.num_qubits} "
+                f"qubits. However, {len(flat_qargs)} were provided."
             )
         yield flat_qargs, []
 
@@ -226,8 +226,8 @@ def validate_parameter(self, parameter):
             if parameter in ["0", "1", "+", "-", "l", "r"]:
                 return parameter
             raise CircuitError(
-                "invalid param label {} for instruction {}. Label should be "
-                "0, 1, +, -, l, or r ".format(type(parameter), self.name)
+                f"invalid param label {type(parameter)} for instruction {self.name}. Label should be "
+                "0, 1, +, -, l, or r "
             )
 
         # StatePreparation instruction parameter can be int, float, and complex.
diff --git a/qiskit/circuit/library/graph_state.py b/qiskit/circuit/library/graph_state.py
index ceefff7971db..89d1edb035ff 100644
--- a/qiskit/circuit/library/graph_state.py
+++ b/qiskit/circuit/library/graph_state.py
@@ -74,7 +74,7 @@ def __init__(self, adjacency_matrix: list | np.ndarray) -> None:
             raise CircuitError("The adjacency matrix must be symmetric.")
 
         num_qubits = len(adjacency_matrix)
-        circuit = QuantumCircuit(num_qubits, name="graph: %s" % (adjacency_matrix))
+        circuit = QuantumCircuit(num_qubits, name=f"graph: {adjacency_matrix}")
 
         circuit.h(range(num_qubits))
         for i in range(num_qubits):
diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py
index 2997d01ed487..d920d7873876 100644
--- a/qiskit/circuit/library/hamiltonian_gate.py
+++ b/qiskit/circuit/library/hamiltonian_gate.py
@@ -103,8 +103,7 @@ def __array__(self, dtype=None, copy=None):
             time = float(self.params[1])
         except TypeError as ex:
             raise TypeError(
-                "Unable to generate Unitary matrix for "
-                "unbound t parameter {}".format(self.params[1])
+                f"Unable to generate Unitary matrix for unbound t parameter {self.params[1]}"
             ) from ex
         arr = scipy.linalg.expm(-1j * self.params[0] * time)
         dtype = complex if dtype is None else dtype
diff --git a/qiskit/circuit/library/hidden_linear_function.py b/qiskit/circuit/library/hidden_linear_function.py
index 1140f1866f08..b68fda7f8fc5 100644
--- a/qiskit/circuit/library/hidden_linear_function.py
+++ b/qiskit/circuit/library/hidden_linear_function.py
@@ -82,7 +82,7 @@ def __init__(self, adjacency_matrix: Union[List[List[int]], np.ndarray]) -> None
             raise CircuitError("The adjacency matrix must be symmetric.")
 
         num_qubits = len(adjacency_matrix)
-        circuit = QuantumCircuit(num_qubits, name="hlf: %s" % adjacency_matrix)
+        circuit = QuantumCircuit(num_qubits, name=f"hlf: {adjacency_matrix}")
 
         circuit.h(range(num_qubits))
         for i in range(num_qubits):
diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py
index 430edfd94f39..25f6c27bbe1b 100644
--- a/qiskit/circuit/library/n_local/n_local.py
+++ b/qiskit/circuit/library/n_local/n_local.py
@@ -441,9 +441,8 @@ def ordered_parameters(self, parameters: ParameterVector | list[Parameter]) -> N
         ):
             raise ValueError(
                 "The length of ordered parameters must be equal to the number of "
-                "settable parameters in the circuit ({}), but is {}".format(
-                    self.num_parameters_settable, len(parameters)
-                )
+                f"settable parameters in the circuit ({self.num_parameters_settable}),"
+                f" but is {len(parameters)}"
             )
         self._ordered_parameters = parameters
         self._invalidate()
diff --git a/qiskit/circuit/library/n_local/qaoa_ansatz.py b/qiskit/circuit/library/n_local/qaoa_ansatz.py
index d62e12c4d941..43869c0c54c9 100644
--- a/qiskit/circuit/library/n_local/qaoa_ansatz.py
+++ b/qiskit/circuit/library/n_local/qaoa_ansatz.py
@@ -97,20 +97,18 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool:
             valid = False
             if raise_on_failure:
                 raise ValueError(
-                    "The number of qubits of the initial state {} does not match "
-                    "the number of qubits of the cost operator {}".format(
-                        self.initial_state.num_qubits, self.num_qubits
-                    )
+                    f"The number of qubits of the initial state {self.initial_state.num_qubits}"
+                    " does not match "
+                    f"the number of qubits of the cost operator {self.num_qubits}"
                 )
 
         if self.mixer_operator is not None and self.mixer_operator.num_qubits != self.num_qubits:
             valid = False
             if raise_on_failure:
                 raise ValueError(
-                    "The number of qubits of the mixer {} does not match "
-                    "the number of qubits of the cost operator {}".format(
-                        self.mixer_operator.num_qubits, self.num_qubits
-                    )
+                    f"The number of qubits of the mixer {self.mixer_operator.num_qubits}"
+                    f" does not match "
+                    f"the number of qubits of the cost operator {self.num_qubits}"
                 )
 
         return valid
diff --git a/qiskit/circuit/library/overlap.py b/qiskit/circuit/library/overlap.py
index 2db6a80eedcc..f6ae5fd6ebdd 100644
--- a/qiskit/circuit/library/overlap.py
+++ b/qiskit/circuit/library/overlap.py
@@ -112,8 +112,6 @@ def _check_unitary(circuit):
     for instruction in circuit.data:
         if not isinstance(instruction.operation, (Gate, Barrier)):
             raise CircuitError(
-                (
-                    "One or more instructions cannot be converted to"
-                    ' a gate. "{}" is not a gate instruction'
-                ).format(instruction.operation.name)
+                "One or more instructions cannot be converted to"
+                f' a gate. "{instruction.operation.name}" is not a gate instruction'
             )
diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py
index 62c1e33b9628..0eef2518a85a 100644
--- a/qiskit/circuit/library/standard_gates/u3.py
+++ b/qiskit/circuit/library/standard_gates/u3.py
@@ -344,7 +344,7 @@ def _generate_gray_code(num_bits):
     result = [0]
     for i in range(num_bits):
         result += [x + 2**i for x in reversed(result)]
-    return [format(x, "0%sb" % num_bits) for x in result]
+    return [format(x, f"0{num_bits}b") for x in result]
 
 
 def _gray_code_chain(q, num_ctrl_qubits, gate):
diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py
index abe4e61adf63..825679f7d4f5 100644
--- a/qiskit/circuit/parameter.py
+++ b/qiskit/circuit/parameter.py
@@ -109,8 +109,8 @@ def subs(self, parameter_map: dict, allow_unknown_parameters: bool = False):
         if allow_unknown_parameters:
             return self
         raise CircuitError(
-            "Cannot bind Parameters ({}) not present in "
-            "expression.".format([str(p) for p in parameter_map])
+            f"Cannot bind Parameters ({[str(p) for p in parameter_map]}) not present in "
+            "expression."
         )
 
     @property
diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
index f881e09333d5..7f839677b904 100644
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -140,7 +140,7 @@ def bind(
             raise ZeroDivisionError(
                 "Binding provided for expression "
                 "results in division by zero "
-                "(Expression: {}, Bindings: {}).".format(self, parameter_values)
+                f"(Expression: {self}, Bindings: {parameter_values})."
             )
 
         return ParameterExpression(free_parameter_symbols, bound_symbol_expr)
@@ -199,8 +199,8 @@ def _raise_if_passed_unknown_parameters(self, parameters):
         unknown_parameters = parameters - self.parameters
         if unknown_parameters:
             raise CircuitError(
-                "Cannot bind Parameters ({}) not present in "
-                "expression.".format([str(p) for p in unknown_parameters])
+                f"Cannot bind Parameters ({[str(p) for p in unknown_parameters]}) not present in "
+                "expression."
             )
 
     def _raise_if_passed_nan(self, parameter_values):
@@ -404,8 +404,8 @@ def __complex__(self):
         except (TypeError, RuntimeError) as exc:
             if self.parameters:
                 raise TypeError(
-                    "ParameterExpression with unbound parameters ({}) "
-                    "cannot be cast to a complex.".format(self.parameters)
+                    f"ParameterExpression with unbound parameters ({self.parameters}) "
+                    "cannot be cast to a complex."
                 ) from None
             raise TypeError("could not cast expression to complex") from exc
 
@@ -416,8 +416,8 @@ def __float__(self):
         except (TypeError, RuntimeError) as exc:
             if self.parameters:
                 raise TypeError(
-                    "ParameterExpression with unbound parameters ({}) "
-                    "cannot be cast to a float.".format(self.parameters)
+                    f"ParameterExpression with unbound parameters ({self.parameters}) "
+                    "cannot be cast to a float."
                 ) from None
             # In symengine, if an expression was complex at any time, its type is likely to have
             # stayed "complex" even when the imaginary part symbolically (i.e. exactly)
@@ -436,8 +436,8 @@ def __int__(self):
         except RuntimeError as exc:
             if self.parameters:
                 raise TypeError(
-                    "ParameterExpression with unbound parameters ({}) "
-                    "cannot be cast to an int.".format(self.parameters)
+                    f"ParameterExpression with unbound parameters ({self.parameters}) "
+                    "cannot be cast to an int."
                 ) from None
             raise TypeError("could not cast expression to int") from exc
 
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index e3fbf40da683..010a91e3639e 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1067,8 +1067,9 @@ def __init__(
 
             if not valid_reg_size:
                 raise CircuitError(
-                    "Circuit args must be Registers or integers. (%s '%s' was "
-                    "provided)" % ([type(reg).__name__ for reg in regs], regs)
+                    "Circuit args must be Registers or integers. ("
+                    f"{[type(reg).__name__ for reg in regs]} '{regs}' was "
+                    "provided)"
                 )
 
             regs = tuple(int(reg) for reg in regs)  # cast to int
@@ -1659,7 +1660,7 @@ def power(
             raise CircuitError(
                 "Cannot raise a parameterized circuit to a non-positive power "
                 "or matrix-power, please bind the free parameters: "
-                "{}".format(self.parameters)
+                f"{self.parameters}"
             )
 
         try:
@@ -2957,14 +2958,14 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None:
                 raise CircuitError(
                     "QuantumCircuit parameters can be Registers or Integers."
                     " If Integers, up to 2 arguments. QuantumCircuit was called"
-                    " with %s." % (regs,)
+                    f" with {(regs,)}."
                 )
 
         for register in regs:
             if isinstance(register, Register) and any(
                 register.name == reg.name for reg in self.qregs + self.cregs
             ):
-                raise CircuitError('register name "%s" already exists' % register.name)
+                raise CircuitError(f'register name "{register.name}" already exists')
 
             if isinstance(register, AncillaRegister):
                 for bit in register:
@@ -3020,7 +3021,7 @@ def add_bits(self, bits: Iterable[Bit]) -> None:
             else:
                 raise CircuitError(
                     "Expected an instance of Qubit, Clbit, or "
-                    "AncillaQubit, but was passed {}".format(bit)
+                    f"AncillaQubit, but was passed {bit}"
                 )
 
     def find_bit(self, bit: Bit) -> BitLocations:
diff --git a/qiskit/circuit/quantumregister.py b/qiskit/circuit/quantumregister.py
index 2ae815b1d172..97d1392698e3 100644
--- a/qiskit/circuit/quantumregister.py
+++ b/qiskit/circuit/quantumregister.py
@@ -43,7 +43,7 @@ def __init__(self, register=None, index=None):
             super().__init__(register, index)
         else:
             raise CircuitError(
-                "Qubit needs a QuantumRegister and %s was provided" % type(register).__name__
+                f"Qubit needs a QuantumRegister and {type(register).__name__} was provided"
             )
 
 
diff --git a/qiskit/circuit/register.py b/qiskit/circuit/register.py
index e927d10e7360..39345705aaef 100644
--- a/qiskit/circuit/register.py
+++ b/qiskit/circuit/register.py
@@ -67,7 +67,7 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None):
         if (size, bits) == (None, None) or (size is not None and bits is not None):
             raise CircuitError(
                 "Exactly one of the size or bits arguments can be "
-                "provided. Provided size=%s bits=%s." % (size, bits)
+                f"provided. Provided size={size} bits={bits}."
             )
 
         # validate (or cast) size
@@ -81,20 +81,18 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None):
 
         if not valid_size:
             raise CircuitError(
-                "Register size must be an integer. (%s '%s' was provided)"
-                % (type(size).__name__, size)
+                f"Register size must be an integer. ({type(size).__name__} '{size}' was provided)"
             )
         size = int(size)  # cast to int
 
         if size < 0:
             raise CircuitError(
-                "Register size must be non-negative (%s '%s' was provided)"
-                % (type(size).__name__, size)
+                f"Register size must be non-negative ({type(size).__name__} '{size}' was provided)"
             )
 
         # validate (or cast) name
         if name is None:
-            name = "%s%i" % (self.prefix, next(self.instances_counter))
+            name = f"{self.prefix}{next(self.instances_counter)}"
         else:
             try:
                 name = str(name)
@@ -108,7 +106,7 @@ def __init__(self, size: int | None = None, name: str | None = None, bits=None):
         self._size = size
 
         self._hash = hash((type(self), self._name, self._size))
-        self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
+        self._repr = f"{self.__class__.__qualname__}({self.size}, '{self.name}')"
         if bits is not None:
             # check duplicated bits
             if self._size != len(set(bits)):
diff --git a/qiskit/circuit/tools/pi_check.py b/qiskit/circuit/tools/pi_check.py
index d3614b747824..334b9683ae9b 100644
--- a/qiskit/circuit/tools/pi_check.py
+++ b/qiskit/circuit/tools/pi_check.py
@@ -104,9 +104,9 @@ def normalize(single_inpt):
             if power[0].shape[0]:
                 if output == "qasm":
                     if ndigits is None:
-                        str_out = "{}".format(single_inpt)
+                        str_out = str(single_inpt)
                     else:
-                        str_out = "{:.{}g}".format(single_inpt, ndigits)
+                        str_out = f"{single_inpt:.{ndigits}g}"
                 elif output == "latex":
                     str_out = f"{neg_str}{pi}^{power[0][0] + 2}"
                 elif output == "mpl":
@@ -119,9 +119,9 @@ def normalize(single_inpt):
         # multiple or power of pi, since no fractions will exceed MAX_FRAC * pi
         if abs(single_inpt) >= (MAX_FRAC * np.pi):
             if ndigits is None:
-                str_out = "{}".format(single_inpt)
+                str_out = str(single_inpt)
             else:
-                str_out = "{:.{}g}".format(single_inpt, ndigits)
+                str_out = f"{single_inpt:.{ndigits}g}"
             return str_out
 
         # Fourth check is for fractions for 1*pi in the numer and any
diff --git a/qiskit/compiler/assembler.py b/qiskit/compiler/assembler.py
index a6c5212e2330..522e1c503ddf 100644
--- a/qiskit/compiler/assembler.py
+++ b/qiskit/compiler/assembler.py
@@ -34,7 +34,7 @@
 
 
 def _log_assembly_time(start_time, end_time):
-    log_msg = "Total Assembly Time - %.5f (ms)" % ((end_time - start_time) * 1000)
+    log_msg = f"Total Assembly Time - {((end_time - start_time) * 1000):.5f} (ms)"
     logger.info(log_msg)
 
 
@@ -311,8 +311,8 @@ def _parse_common_args(
         raise QiskitError("Argument 'shots' should be of type 'int'")
     elif max_shots and max_shots < shots:
         raise QiskitError(
-            "Number of shots specified: %s exceeds max_shots property of the "
-            "backend: %s." % (shots, max_shots)
+            f"Number of shots specified: {max_shots} exceeds max_shots property of the "
+            f"backend: {max_shots}."
         )
 
     dynamic_reprate_enabled = getattr(backend_config, "dynamic_reprate_enabled", False)
@@ -397,9 +397,8 @@ def _check_lo_freqs(
                 raise QiskitError(f"Each element of {lo_type} LO range must be a 2d list.")
             if freq < freq_range[0] or freq > freq_range[1]:
                 raise QiskitError(
-                    "Qubit {} {} LO frequency is {}. The range is [{}, {}].".format(
-                        i, lo_type, freq, freq_range[0], freq_range[1]
-                    )
+                    f"Qubit {i} {lo_type} LO frequency is {freq}. "
+                    f"The range is [{freq_range[0]}, {freq_range[1]}]."
                 )
 
 
@@ -429,9 +428,8 @@ def _parse_pulse_args(
 
         if meas_level not in getattr(backend_config, "meas_levels", [MeasLevel.CLASSIFIED]):
             raise QiskitError(
-                ("meas_level = {} not supported for backend {}, only {} is supported").format(
-                    meas_level, backend_config.backend_name, backend_config.meas_levels
-                )
+                f"meas_level = {meas_level} not supported for backend "
+                f"{backend_config.backend_name}, only {backend_config.meas_levels} is supported"
             )
 
     meas_map = meas_map or getattr(backend_config, "meas_map", None)
@@ -522,14 +520,12 @@ def _parse_rep_delay(
         if rep_delay_range is not None and isinstance(rep_delay_range, list):
             if len(rep_delay_range) != 2:
                 raise QiskitError(
-                    "Backend rep_delay_range {} must be a list with two entries.".format(
-                        rep_delay_range
-                    )
+                    f"Backend rep_delay_range {rep_delay_range} must be a list with two entries."
                 )
             if not rep_delay_range[0] <= rep_delay <= rep_delay_range[1]:
                 raise QiskitError(
-                    "Supplied rep delay {} not in the supported "
-                    "backend range {}".format(rep_delay, rep_delay_range)
+                    f"Supplied rep delay {rep_delay} not in the supported "
+                    f"backend range {rep_delay_range}"
                 )
         rep_delay = rep_delay * 1e6  # convert sec to μs
 
diff --git a/qiskit/compiler/scheduler.py b/qiskit/compiler/scheduler.py
index 0a30b07a49b1..f141902b7062 100644
--- a/qiskit/compiler/scheduler.py
+++ b/qiskit/compiler/scheduler.py
@@ -31,7 +31,7 @@
 
 
 def _log_schedule_time(start_time, end_time):
-    log_msg = "Total Scheduling Time - %.5f (ms)" % ((end_time - start_time) * 1000)
+    log_msg = f"Total Scheduling Time - {((end_time - start_time) * 1000):.5f} (ms)"
     logger.info(log_msg)
 
 
diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py
index 9dd316839a0f..183e260739ba 100644
--- a/qiskit/compiler/transpiler.py
+++ b/qiskit/compiler/transpiler.py
@@ -405,7 +405,7 @@ def _check_circuits_coupling_map(circuits, cmap, backend):
 
 
 def _log_transpile_time(start_time, end_time):
-    log_msg = "Total Transpile Time - %.5f (ms)" % ((end_time - start_time) * 1000)
+    log_msg = f"Total Transpile Time - {((end_time - start_time) * 1000):.5f} (ms)"
     logger.info(log_msg)
 
 
@@ -476,7 +476,7 @@ def _parse_output_name(output_name, circuits):
         else:
             raise TranspilerError(
                 "The parameter output_name should be a string or a"
-                "list of strings: %s was used." % type(output_name)
+                f"list of strings: {type(output_name)} was used."
             )
     else:
         return [circuit.name for circuit in circuits]
diff --git a/qiskit/converters/circuit_to_gate.py b/qiskit/converters/circuit_to_gate.py
index 39eed1053eb1..c9f9ac6e1aff 100644
--- a/qiskit/converters/circuit_to_gate.py
+++ b/qiskit/converters/circuit_to_gate.py
@@ -64,10 +64,8 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label
     for instruction in circuit.data:
         if not _check_is_gate(instruction.operation):
             raise QiskitError(
-                (
-                    "One or more instructions cannot be converted to"
-                    ' a gate. "{}" is not a gate instruction'
-                ).format(instruction.operation.name)
+                "One or more instructions cannot be converted to"
+                f' a gate. "{instruction.operation.name}" is not a gate instruction'
             )
 
     if parameter_map is None:
@@ -77,10 +75,8 @@ def circuit_to_gate(circuit, parameter_map=None, equivalence_library=None, label
 
     if parameter_dict.keys() != circuit.parameters:
         raise QiskitError(
-            (
-                "parameter_map should map all circuit parameters. "
-                "Circuit parameters: {}, parameter_map: {}"
-            ).format(circuit.parameters, parameter_dict)
+            "parameter_map should map all circuit parameters. "
+            f"Circuit parameters: {circuit.parameters}, parameter_map: {parameter_dict}"
         )
 
     gate = Gate(
diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py
index 1a5907c3ec84..571e330eb0db 100644
--- a/qiskit/converters/circuit_to_instruction.py
+++ b/qiskit/converters/circuit_to_instruction.py
@@ -89,10 +89,8 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None
 
     if parameter_dict.keys() != circuit.parameters:
         raise QiskitError(
-            (
-                "parameter_map should map all circuit parameters. "
-                "Circuit parameters: {}, parameter_map: {}"
-            ).format(circuit.parameters, parameter_dict)
+            "parameter_map should map all circuit parameters. "
+            f"Circuit parameters: {circuit.parameters}, parameter_map: {parameter_dict}"
         )
 
     out_instruction = Instruction(
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index 96562a37b151..686951f26fc2 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -271,7 +271,7 @@ def add_qubits(self, qubits):
 
         duplicate_qubits = set(self.qubits).intersection(qubits)
         if duplicate_qubits:
-            raise DAGCircuitError("duplicate qubits %s" % duplicate_qubits)
+            raise DAGCircuitError(f"duplicate qubits {duplicate_qubits}")
 
         for qubit in qubits:
             self.qubits.append(qubit)
@@ -285,7 +285,7 @@ def add_clbits(self, clbits):
 
         duplicate_clbits = set(self.clbits).intersection(clbits)
         if duplicate_clbits:
-            raise DAGCircuitError("duplicate clbits %s" % duplicate_clbits)
+            raise DAGCircuitError(f"duplicate clbits {duplicate_clbits}")
 
         for clbit in clbits:
             self.clbits.append(clbit)
@@ -297,7 +297,7 @@ def add_qreg(self, qreg):
         if not isinstance(qreg, QuantumRegister):
             raise DAGCircuitError("not a QuantumRegister instance.")
         if qreg.name in self.qregs:
-            raise DAGCircuitError("duplicate register %s" % qreg.name)
+            raise DAGCircuitError(f"duplicate register {qreg.name}")
         self.qregs[qreg.name] = qreg
         existing_qubits = set(self.qubits)
         for j in range(qreg.size):
@@ -315,7 +315,7 @@ def add_creg(self, creg):
         if not isinstance(creg, ClassicalRegister):
             raise DAGCircuitError("not a ClassicalRegister instance.")
         if creg.name in self.cregs:
-            raise DAGCircuitError("duplicate register %s" % creg.name)
+            raise DAGCircuitError(f"duplicate register {creg.name}")
         self.cregs[creg.name] = creg
         existing_clbits = set(self.clbits)
         for j in range(creg.size):
@@ -451,17 +451,17 @@ def remove_clbits(self, *clbits):
         """
         if any(not isinstance(clbit, Clbit) for clbit in clbits):
             raise DAGCircuitError(
-                "clbits not of type Clbit: %s" % [b for b in clbits if not isinstance(b, Clbit)]
+                f"clbits not of type Clbit: {[b for b in clbits if not isinstance(b, Clbit)]}"
             )
 
         clbits = set(clbits)
         unknown_clbits = clbits.difference(self.clbits)
         if unknown_clbits:
-            raise DAGCircuitError("clbits not in circuit: %s" % unknown_clbits)
+            raise DAGCircuitError(f"clbits not in circuit: {unknown_clbits}")
 
         busy_clbits = {bit for bit in clbits if not self._is_wire_idle(bit)}
         if busy_clbits:
-            raise DAGCircuitError("clbits not idle: %s" % busy_clbits)
+            raise DAGCircuitError(f"clbits not idle: {busy_clbits}")
 
         # remove any references to bits
         cregs_to_remove = {creg for creg in self.cregs.values() if not clbits.isdisjoint(creg)}
@@ -487,13 +487,13 @@ def remove_cregs(self, *cregs):
         """
         if any(not isinstance(creg, ClassicalRegister) for creg in cregs):
             raise DAGCircuitError(
-                "cregs not of type ClassicalRegister: %s"
-                % [r for r in cregs if not isinstance(r, ClassicalRegister)]
+                "cregs not of type ClassicalRegister: "
+                f"{[r for r in cregs if not isinstance(r, ClassicalRegister)]}"
             )
 
         unknown_cregs = set(cregs).difference(self.cregs.values())
         if unknown_cregs:
-            raise DAGCircuitError("cregs not in circuit: %s" % unknown_cregs)
+            raise DAGCircuitError(f"cregs not in circuit: {unknown_cregs}")
 
         for creg in cregs:
             del self.cregs[creg.name]
@@ -517,17 +517,17 @@ def remove_qubits(self, *qubits):
         """
         if any(not isinstance(qubit, Qubit) for qubit in qubits):
             raise DAGCircuitError(
-                "qubits not of type Qubit: %s" % [b for b in qubits if not isinstance(b, Qubit)]
+                f"qubits not of type Qubit: {[b for b in qubits if not isinstance(b, Qubit)]}"
             )
 
         qubits = set(qubits)
         unknown_qubits = qubits.difference(self.qubits)
         if unknown_qubits:
-            raise DAGCircuitError("qubits not in circuit: %s" % unknown_qubits)
+            raise DAGCircuitError(f"qubits not in circuit: {unknown_qubits}")
 
         busy_qubits = {bit for bit in qubits if not self._is_wire_idle(bit)}
         if busy_qubits:
-            raise DAGCircuitError("qubits not idle: %s" % busy_qubits)
+            raise DAGCircuitError(f"qubits not idle: {busy_qubits}")
 
         # remove any references to bits
         qregs_to_remove = {qreg for qreg in self.qregs.values() if not qubits.isdisjoint(qreg)}
@@ -553,13 +553,13 @@ def remove_qregs(self, *qregs):
         """
         if any(not isinstance(qreg, QuantumRegister) for qreg in qregs):
             raise DAGCircuitError(
-                "qregs not of type QuantumRegister: %s"
-                % [r for r in qregs if not isinstance(r, QuantumRegister)]
+                f"qregs not of type QuantumRegister: "
+                f"{[r for r in qregs if not isinstance(r, QuantumRegister)]}"
             )
 
         unknown_qregs = set(qregs).difference(self.qregs.values())
         if unknown_qregs:
-            raise DAGCircuitError("qregs not in circuit: %s" % unknown_qregs)
+            raise DAGCircuitError(f"qregs not in circuit: {unknown_qregs}")
 
         for qreg in qregs:
             del self.qregs[qreg.name]
@@ -581,13 +581,13 @@ def _is_wire_idle(self, wire):
             DAGCircuitError: the wire is not in the circuit.
         """
         if wire not in self._wires:
-            raise DAGCircuitError("wire %s not in circuit" % wire)
+            raise DAGCircuitError(f"wire {wire} not in circuit")
 
         try:
             child = next(self.successors(self.input_map[wire]))
         except StopIteration as e:
             raise DAGCircuitError(
-                "Invalid dagcircuit input node %s has no output" % self.input_map[wire]
+                f"Invalid dagcircuit input node {self.input_map[wire]} has no output"
             ) from e
         return child is self.output_map[wire]
 
@@ -950,12 +950,11 @@ def _reject_new_register(reg):
                     # the mapped wire should already exist
                     if m_wire not in dag.output_map:
                         raise DAGCircuitError(
-                            "wire %s[%d] not in self" % (m_wire.register.name, m_wire.index)
+                            f"wire {m_wire.register.name}[{m_wire.index}] not in self"
                         )
                     if nd.wire not in other._wires:
                         raise DAGCircuitError(
-                            "inconsistent wire type for %s[%d] in other"
-                            % (nd.register.name, nd.wire.index)
+                            f"inconsistent wire type for {nd.register.name}[{nd.wire.index}] in other"
                         )
                 # If it's a Var wire, we already checked that it exists in the destination.
             elif isinstance(nd, DAGOutNode):
@@ -974,7 +973,7 @@ def _reject_new_register(reg):
                     op.target = variable_mapper.map_target(op.target)
                 dag.apply_operation_back(op, m_qargs, m_cargs, check=False)
             else:
-                raise DAGCircuitError("bad node type %s" % type(nd))
+                raise DAGCircuitError(f"bad node type {type(nd)}")
 
         if not inplace:
             return dag
@@ -1632,10 +1631,10 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_
 
         if node.op.num_qubits != op.num_qubits or node.op.num_clbits != op.num_clbits:
             raise DAGCircuitError(
-                "Cannot replace node of width ({} qubits, {} clbits) with "
-                "operation of mismatched width ({} qubits, {} clbits).".format(
-                    node.op.num_qubits, node.op.num_clbits, op.num_qubits, op.num_clbits
-                )
+                f"Cannot replace node of width ({node.op.num_qubits} qubits, "
+                f"{node.op.num_clbits} clbits) with "
+                f"operation of mismatched width ({op.num_qubits} qubits, "
+                f"{op.num_clbits} clbits)."
             )
 
         # This might include wires that are inherent to the node, like in its `condition` or
@@ -1953,8 +1952,8 @@ def remove_op_node(self, node):
         """
         if not isinstance(node, DAGOpNode):
             raise DAGCircuitError(
-                'The method remove_op_node only works on DAGOpNodes. A "%s" '
-                "node type was wrongly provided." % type(node)
+                f'The method remove_op_node only works on DAGOpNodes. A "{type(node)}" '
+                "node type was wrongly provided."
             )
 
         self._multi_graph.remove_node_retain_edges(
@@ -2182,7 +2181,7 @@ def nodes_on_wire(self, wire, only_ops=False):
         current_node = self.input_map.get(wire, None)
 
         if not current_node:
-            raise DAGCircuitError("The given wire %s is not present in the circuit" % str(wire))
+            raise DAGCircuitError(f"The given wire {str(wire)} is not present in the circuit")
 
         more_nodes = True
         while more_nodes:
diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py
index 4316c9471401..63b91f920631 100644
--- a/qiskit/dagcircuit/dagdependency.py
+++ b/qiskit/dagcircuit/dagdependency.py
@@ -187,7 +187,7 @@ def add_qubits(self, qubits):
 
         duplicate_qubits = set(self.qubits).intersection(qubits)
         if duplicate_qubits:
-            raise DAGDependencyError("duplicate qubits %s" % duplicate_qubits)
+            raise DAGDependencyError(f"duplicate qubits {duplicate_qubits}")
 
         self.qubits.extend(qubits)
 
@@ -198,7 +198,7 @@ def add_clbits(self, clbits):
 
         duplicate_clbits = set(self.clbits).intersection(clbits)
         if duplicate_clbits:
-            raise DAGDependencyError("duplicate clbits %s" % duplicate_clbits)
+            raise DAGDependencyError(f"duplicate clbits {duplicate_clbits}")
 
         self.clbits.extend(clbits)
 
@@ -207,7 +207,7 @@ def add_qreg(self, qreg):
         if not isinstance(qreg, QuantumRegister):
             raise DAGDependencyError("not a QuantumRegister instance.")
         if qreg.name in self.qregs:
-            raise DAGDependencyError("duplicate register %s" % qreg.name)
+            raise DAGDependencyError(f"duplicate register {qreg.name}")
         self.qregs[qreg.name] = qreg
         existing_qubits = set(self.qubits)
         for j in range(qreg.size):
@@ -219,7 +219,7 @@ def add_creg(self, creg):
         if not isinstance(creg, ClassicalRegister):
             raise DAGDependencyError("not a ClassicalRegister instance.")
         if creg.name in self.cregs:
-            raise DAGDependencyError("duplicate register %s" % creg.name)
+            raise DAGDependencyError(f"duplicate register {creg.name}")
         self.cregs[creg.name] = creg
         existing_clbits = set(self.clbits)
         for j in range(creg.size):
diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py
index cb5d447162cb..e50c47b24f90 100644
--- a/qiskit/dagcircuit/dagdependency_v2.py
+++ b/qiskit/dagcircuit/dagdependency_v2.py
@@ -247,7 +247,7 @@ def add_qubits(self, qubits):
 
         duplicate_qubits = set(self.qubits).intersection(qubits)
         if duplicate_qubits:
-            raise DAGDependencyError("duplicate qubits %s" % duplicate_qubits)
+            raise DAGDependencyError(f"duplicate qubits {duplicate_qubits}")
 
         for qubit in qubits:
             self.qubits.append(qubit)
@@ -260,7 +260,7 @@ def add_clbits(self, clbits):
 
         duplicate_clbits = set(self.clbits).intersection(clbits)
         if duplicate_clbits:
-            raise DAGDependencyError("duplicate clbits %s" % duplicate_clbits)
+            raise DAGDependencyError(f"duplicate clbits {duplicate_clbits}")
 
         for clbit in clbits:
             self.clbits.append(clbit)
@@ -271,7 +271,7 @@ def add_qreg(self, qreg):
         if not isinstance(qreg, QuantumRegister):
             raise DAGDependencyError("not a QuantumRegister instance.")
         if qreg.name in self.qregs:
-            raise DAGDependencyError("duplicate register %s" % qreg.name)
+            raise DAGDependencyError(f"duplicate register {qreg.name}")
         self.qregs[qreg.name] = qreg
         existing_qubits = set(self.qubits)
         for j in range(qreg.size):
@@ -288,7 +288,7 @@ def add_creg(self, creg):
         if not isinstance(creg, ClassicalRegister):
             raise DAGDependencyError("not a ClassicalRegister instance.")
         if creg.name in self.cregs:
-            raise DAGDependencyError("duplicate register %s" % creg.name)
+            raise DAGDependencyError(f"duplicate register {creg.name}")
         self.cregs[creg.name] = creg
         existing_clbits = set(self.clbits)
         for j in range(creg.size):
diff --git a/qiskit/dagcircuit/dagdepnode.py b/qiskit/dagcircuit/dagdepnode.py
index fe63f57d3d4f..cc00db9725c0 100644
--- a/qiskit/dagcircuit/dagdepnode.py
+++ b/qiskit/dagcircuit/dagdepnode.py
@@ -83,7 +83,7 @@ def __init__(
     def op(self):
         """Returns the Instruction object corresponding to the op for the node, else None"""
         if not self.type or self.type != "op":
-            raise QiskitError("The node %s is not an op node" % (str(self)))
+            raise QiskitError(f"The node {str(self)} is not an op node")
         return self._op
 
     @op.setter
diff --git a/qiskit/passmanager/flow_controllers.py b/qiskit/passmanager/flow_controllers.py
index c7d952d048dd..dcfcba704587 100644
--- a/qiskit/passmanager/flow_controllers.py
+++ b/qiskit/passmanager/flow_controllers.py
@@ -84,7 +84,7 @@ def iter_tasks(self, state: PassManagerState) -> Generator[Task, PassManagerStat
                 return
             # Remove stored tasks from the completed task collection for next loop
             state.workflow_status.completed_passes.difference_update(self.tasks)
-        raise PassManagerError("Maximum iteration reached. max_iteration=%i" % max_iteration)
+        raise PassManagerError(f"Maximum iteration reached. max_iteration={max_iteration}")
 
 
 class ConditionalController(BaseController):
diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py
index 8d3a4e9aa693..99527bf584ee 100644
--- a/qiskit/passmanager/passmanager.py
+++ b/qiskit/passmanager/passmanager.py
@@ -130,7 +130,7 @@ def __add__(self, other):
                 return new_passmanager
             except PassManagerError as ex:
                 raise TypeError(
-                    "unsupported operand type + for %s and %s" % (self.__class__, other.__class__)
+                    f"unsupported operand type + for {self.__class__} and {other.__class__}"
                 ) from ex
 
     @abstractmethod
diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py
index 2e551cc311e8..ed9fd3fdbb87 100644
--- a/qiskit/providers/backend.py
+++ b/qiskit/providers/backend.py
@@ -95,7 +95,7 @@ def __init__(self, configuration, provider=None, **fields):
         if fields:
             for field in fields:
                 if field not in self._options.data:
-                    raise AttributeError("Options field %s is not valid for this backend" % field)
+                    raise AttributeError(f"Options field {field} is not valid for this backend")
             self._options.update_config(**fields)
 
     @classmethod
@@ -129,7 +129,7 @@ def set_options(self, **fields):
         """
         for field in fields:
             if not hasattr(self._options, field):
-                raise AttributeError("Options field %s is not valid for this backend" % field)
+                raise AttributeError(f"Options field {field} is not valid for this backend")
         self._options.update_options(**fields)
 
     def configuration(self):
@@ -352,7 +352,7 @@ def __init__(
         if fields:
             for field in fields:
                 if field not in self._options.data:
-                    raise AttributeError("Options field %s is not valid for this backend" % field)
+                    raise AttributeError(f"Options field {field} is not valid for this backend")
             self._options.update_config(**fields)
         self.name = name
         """Name of the backend."""
@@ -598,7 +598,7 @@ def set_options(self, **fields):
         """
         for field in fields:
             if not hasattr(self._options, field):
-                raise AttributeError("Options field %s is not valid for this backend" % field)
+                raise AttributeError(f"Options field {field} is not valid for this backend")
         self._options.update_options(**fields)
 
     @property
diff --git a/qiskit/providers/basic_provider/basic_provider_tools.py b/qiskit/providers/basic_provider/basic_provider_tools.py
index b2670cc0977f..786815dda534 100644
--- a/qiskit/providers/basic_provider/basic_provider_tools.py
+++ b/qiskit/providers/basic_provider/basic_provider_tools.py
@@ -66,7 +66,7 @@ def single_gate_matrix(gate: str, params: list[float] | None = None) -> np.ndarr
     if gate in SINGLE_QUBIT_GATES:
         gc = SINGLE_QUBIT_GATES[gate]
     else:
-        raise QiskitError("Gate is not a valid basis gate for this simulator: %s" % gate)
+        raise QiskitError(f"Gate is not a valid basis gate for this simulator: {gate}")
 
     return gc(*params).to_matrix()
 
diff --git a/qiskit/providers/basic_provider/basic_simulator.py b/qiskit/providers/basic_provider/basic_simulator.py
index 978e1dad56fd..9971bf36725c 100644
--- a/qiskit/providers/basic_provider/basic_simulator.py
+++ b/qiskit/providers/basic_provider/basic_simulator.py
@@ -208,7 +208,7 @@ def _build_basic_target(self) -> Target:
                 target.add_instruction(UnitaryGate, name="unitary")
             else:
                 raise BasicProviderError(
-                    "Gate is not a valid basis gate for this simulator: %s" % name
+                    f"Gate is not a valid basis gate for this simulator: {name}"
                 )
         return target
 
@@ -531,7 +531,7 @@ def run(
         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
+                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
                 )
             else:
                 out_options[key] = value
diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py
index d84aba46371d..4a638f315574 100644
--- a/qiskit/providers/fake_provider/fake_backend.py
+++ b/qiskit/providers/fake_provider/fake_backend.py
@@ -143,8 +143,8 @@ def run(self, run_input, **kwargs):
                     pulse_job = False
         if pulse_job is None:
             raise QiskitError(
-                "Invalid input object %s, must be either a "
-                "QuantumCircuit, Schedule, or a list of either" % circuits
+                f"Invalid input object {circuits}, must be either a "
+                "QuantumCircuit, Schedule, or a list of either"
             )
         if pulse_job:
             raise QiskitError("Pulse simulation is currently not supported for fake backends.")
diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py
index e806c75ea3a5..1ac0484d775d 100644
--- a/qiskit/providers/fake_provider/generic_backend_v2.py
+++ b/qiskit/providers/fake_provider/generic_backend_v2.py
@@ -496,8 +496,8 @@ def run(self, run_input, **options):
                     pulse_job = False
         if pulse_job is None:  # submitted job is invalid
             raise QiskitError(
-                "Invalid input object %s, must be either a "
-                "QuantumCircuit, Schedule, or a list of either" % circuits
+                f"Invalid input object {circuits}, must be either a "
+                "QuantumCircuit, Schedule, or a list of either"
             )
         if pulse_job:  # pulse job
             raise QiskitError("Pulse simulation is currently not supported for V2 backends.")
diff --git a/qiskit/providers/models/backendproperties.py b/qiskit/providers/models/backendproperties.py
index 3b5b9c5e010b..332aac7c5edd 100644
--- a/qiskit/providers/models/backendproperties.py
+++ b/qiskit/providers/models/backendproperties.py
@@ -404,9 +404,9 @@ def qubit_property(
             if name is not None:
                 result = result[name]
         except KeyError as ex:
+            formatted_name = "y '" + name + "'" if name else "ies"
             raise BackendPropertyError(
-                "Couldn't find the propert{name} for qubit "
-                "{qubit}.".format(name="y '" + name + "'" if name else "ies", qubit=qubit)
+                f"Couldn't find the propert{formatted_name} for qubit {qubit}."
             ) from ex
         return result
 
diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py
index 13becb1c956d..7c1864bad9ee 100644
--- a/qiskit/providers/models/pulsedefaults.py
+++ b/qiskit/providers/models/pulsedefaults.py
@@ -296,9 +296,4 @@ def __str__(self):
         meas_freqs = [freq / 1e9 for freq in self.meas_freq_est]
         qfreq = f"Qubit Frequencies [GHz]\n{qubit_freqs}"
         mfreq = f"Measurement Frequencies [GHz]\n{meas_freqs} "
-        return "<{name}({insts}{qfreq}\n{mfreq})>".format(
-            name=self.__class__.__name__,
-            insts=str(self.instruction_schedule_map),
-            qfreq=qfreq,
-            mfreq=mfreq,
-        )
+        return f"<{self.__class__.__name__}({str(self.instruction_schedule_map)}{qfreq}\n{mfreq})>"
diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py
index 8b2bffc52d84..fe4e7303a674 100644
--- a/qiskit/providers/options.py
+++ b/qiskit/providers/options.py
@@ -170,7 +170,7 @@ def __init__(self, **kwargs):
 
     def __repr__(self):
         items = (f"{k}={v!r}" for k, v in self._fields.items())
-        return "{}({})".format(type(self).__name__, ", ".join(items))
+        return f"{type(self).__name__}({', '.join(items)})"
 
     def __eq__(self, other):
         if isinstance(self, Options) and isinstance(other, Options):
@@ -211,7 +211,7 @@ def set_validator(self, field, validator_value):
         """
 
         if field not in self._fields:
-            raise KeyError("Field '%s' is not present in this options object" % field)
+            raise KeyError(f"Field '{field}' is not present in this options object")
         if isinstance(validator_value, tuple):
             if len(validator_value) != 2:
                 raise ValueError(
diff --git a/qiskit/pulse/configuration.py b/qiskit/pulse/configuration.py
index 4668152973f2..1bfd1f13e2ee 100644
--- a/qiskit/pulse/configuration.py
+++ b/qiskit/pulse/configuration.py
@@ -55,11 +55,9 @@ def __init__(self, name: str | None = None, **params):
         self.params = params
 
     def __repr__(self):
-        return "{}({}{})".format(
-            self.__class__.__name__,
-            "'" + self.name + "', " or "",
-            ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()),
-        )
+        name_repr = "'" + self.name + "', "
+        params_repr = ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items())
+        return f"{self.__class__.__name__}({name_repr}{params_repr})"
 
     def __eq__(self, other):
         if isinstance(other, Kernel):
@@ -83,11 +81,9 @@ def __init__(self, name: str | None = None, **params):
         self.params = params
 
     def __repr__(self):
-        return "{}({}{})".format(
-            self.__class__.__name__,
-            "'" + self.name + "', " or "",
-            ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items()),
-        )
+        name_repr = "'" + self.name + "', " or ""
+        params_repr = ", ".join(f"{str(k)}={str(v)}" for k, v in self.params.items())
+        return f"{self.__class__.__name__}({name_repr}{params_repr})"
 
     def __eq__(self, other):
         if isinstance(other, Discriminator):
@@ -184,7 +180,7 @@ def add_lo(self, channel: DriveChannel | MeasureChannel, freq: float):
             self.check_lo(channel, freq)
             self._m_lo_freq[channel] = freq
         else:
-            raise PulseError("Specified channel %s cannot be configured." % channel.name)
+            raise PulseError(f"Specified channel {channel.name} cannot be configured.")
 
     def add_lo_range(
         self, channel: DriveChannel | MeasureChannel, lo_range: LoRange | tuple[int, int]
@@ -236,7 +232,7 @@ def channel_lo(self, channel: DriveChannel | MeasureChannel) -> float:
             if channel in self.meas_los:
                 return self.meas_los[channel]
 
-        raise PulseError("Channel %s is not configured" % channel)
+        raise PulseError(f"Channel {channel} is not configured")
 
     @property
     def qubit_los(self) -> dict[DriveChannel, float]:
diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py
index decda15c9947..afa71b6825a7 100644
--- a/qiskit/pulse/instruction_schedule_map.py
+++ b/qiskit/pulse/instruction_schedule_map.py
@@ -169,10 +169,8 @@ def assert_has(
         if not self.has(instruction, _to_tuple(qubits)):
             if instruction in self._map:
                 raise PulseError(
-                    "Operation '{inst}' exists, but is only defined for qubits "
-                    "{qubits}.".format(
-                        inst=instruction, qubits=self.qubits_with_instruction(instruction)
-                    )
+                    f"Operation '{instruction}' exists, but is only defined for qubits "
+                    f"{self.qubits_with_instruction(instruction)}."
                 )
             raise PulseError(f"Operation '{instruction}' is not defined for this system.")
 
diff --git a/qiskit/pulse/instructions/acquire.py b/qiskit/pulse/instructions/acquire.py
index 066163a79b0e..98fbf460c1b3 100644
--- a/qiskit/pulse/instructions/acquire.py
+++ b/qiskit/pulse/instructions/acquire.py
@@ -138,12 +138,11 @@ def is_parameterized(self) -> bool:
         return isinstance(self.duration, ParameterExpression) or super().is_parameterized()
 
     def __repr__(self) -> str:
-        return "{}({}{}{}{}{}{})".format(
-            self.__class__.__name__,
-            self.duration,
-            ", " + str(self.channel),
-            ", " + str(self.mem_slot) if self.mem_slot else "",
-            ", " + str(self.reg_slot) if self.reg_slot else "",
-            ", " + str(self.kernel) if self.kernel else "",
-            ", " + str(self.discriminator) if self.discriminator else "",
+        mem_slot_repr = str(self.mem_slot) if self.mem_slot else ""
+        reg_slot_repr = str(self.reg_slot) if self.reg_slot else ""
+        kernel_repr = str(self.kernel) if self.kernel else ""
+        discriminator_repr = str(self.discriminator) if self.discriminator else ""
+        return (
+            f"{self.__class__.__name__}({self.duration}, {str(self.channel)}, "
+            f"{mem_slot_repr}, {reg_slot_repr}, {kernel_repr}, {discriminator_repr})"
         )
diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py
index ece20545b504..61ebe67777f8 100644
--- a/qiskit/pulse/instructions/instruction.py
+++ b/qiskit/pulse/instructions/instruction.py
@@ -264,6 +264,5 @@ def __lshift__(self, time: int):
 
     def __repr__(self) -> str:
         operands = ", ".join(str(op) for op in self.operands)
-        return "{}({}{})".format(
-            self.__class__.__name__, operands, f", name='{self.name}'" if self.name else ""
-        )
+        name_repr = f", name='{self.name}'" if self.name else ""
+        return f"{self.__class__.__name__}({operands}{name_repr})"
diff --git a/qiskit/pulse/library/samplers/decorators.py b/qiskit/pulse/library/samplers/decorators.py
index ac78fba85954..db6aabd7b1de 100644
--- a/qiskit/pulse/library/samplers/decorators.py
+++ b/qiskit/pulse/library/samplers/decorators.py
@@ -182,9 +182,9 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca
     header, body = wrapped_docstring.split("\n", 1)
     body = textwrap.indent(body, "                    ")
     wrapped_docstring = header + body
-    updated_ds = """
-                Discretized continuous pulse function: `{continuous_name}` using
-                sampler: `{sampler_name}`.
+    updated_ds = f"""
+                Discretized continuous pulse function: `{discretized_pulse.__name__}` using
+                sampler: `{sampler_inst.__name__}`.
 
                  The first argument (time) of the continuous pulse function has been replaced with
                  a discretized `duration` of type (int).
@@ -198,12 +198,8 @@ def _update_docstring(discretized_pulse: Callable, sampler_inst: Callable) -> Ca
 
                  Sampled continuous function:
 
-                    {continuous_doc}
-                """.format(
-        continuous_name=discretized_pulse.__name__,
-        sampler_name=sampler_inst.__name__,
-        continuous_doc=wrapped_docstring,
-    )
+                    {wrapped_docstring}
+                """
 
     discretized_pulse.__doc__ = updated_ds
     return discretized_pulse
diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py
index b076bcf56cb0..33d428771b22 100644
--- a/qiskit/pulse/library/symbolic_pulses.py
+++ b/qiskit/pulse/library/symbolic_pulses.py
@@ -570,11 +570,8 @@ def __eq__(self, other: object) -> bool:
 
     def __repr__(self) -> str:
         param_repr = ", ".join(f"{p}={v}" for p, v in self.parameters.items())
-        return "{}({}{})".format(
-            self._pulse_type,
-            param_repr,
-            f", name='{self.name}'" if self.name is not None else "",
-        )
+        name_repr = f", name='{self.name}'" if self.name is not None else ""
+        return f"{self._pulse_type}({param_repr}{name_repr})"
 
     __hash__ = None
 
diff --git a/qiskit/pulse/library/waveform.py b/qiskit/pulse/library/waveform.py
index e9ad9bcbc717..ad852f226ac2 100644
--- a/qiskit/pulse/library/waveform.py
+++ b/qiskit/pulse/library/waveform.py
@@ -130,8 +130,5 @@ def __repr__(self) -> str:
         opt = np.get_printoptions()
         np.set_printoptions(threshold=50)
         np.set_printoptions(**opt)
-        return "{}({}{})".format(
-            self.__class__.__name__,
-            repr(self.samples),
-            f", name='{self.name}'" if self.name is not None else "",
-        )
+        name_repr = f", name='{self.name}'" if self.name is not None else ""
+        return f"{self.__class__.__name__}({repr(self.samples)}{name_repr})"
diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py
index 88414cfc7e9b..3a39932e5b10 100644
--- a/qiskit/pulse/macros.py
+++ b/qiskit/pulse/macros.py
@@ -135,10 +135,10 @@ def _measure_v1(
             default_sched = inst_map.get(measure_name, measure_group_qubits)
         except exceptions.PulseError as ex:
             raise exceptions.PulseError(
-                "We could not find a default measurement schedule called '{}'. "
+                f"We could not find a default measurement schedule called '{measure_name}'. "
                 "Please provide another name using the 'measure_name' keyword "
                 "argument. For assistance, the instructions which are defined are: "
-                "{}".format(measure_name, inst_map.instructions)
+                f"{inst_map.instructions}"
             ) from ex
         for time, inst in default_sched.instructions:
             if inst.channel.index not in qubits:
@@ -203,10 +203,10 @@ def _measure_v2(
                 schedule += _schedule_remapping_memory_slot(default_sched, qubit_mem_slots)
         except KeyError as ex:
             raise exceptions.PulseError(
-                "We could not find a default measurement schedule called '{}'. "
+                f"We could not find a default measurement schedule called '{measure_name}'. "
                 "Please provide another name using the 'measure_name' keyword "
                 "argument. For assistance, the instructions which are defined are: "
-                "{}".format(measure_name, target.instructions)
+                f"{target.instructions}"
             ) from ex
     return schedule
 
diff --git a/qiskit/pulse/parser.py b/qiskit/pulse/parser.py
index 8e31faebf77a..e9cd4917a7ce 100644
--- a/qiskit/pulse/parser.py
+++ b/qiskit/pulse/parser.py
@@ -124,13 +124,11 @@ def __call__(self, *args, **kwargs) -> complex | ast.Expression | PulseExpressio
                         self._locals_dict[key] = val
                     else:
                         raise PulseError(
-                            "%s got multiple values for argument '%s'"
-                            % (self.__class__.__name__, key)
+                            f"{self.__class__.__name__} got multiple values for argument '{key}'"
                         )
                 else:
                     raise PulseError(
-                        "%s got an unexpected keyword argument '%s'"
-                        % (self.__class__.__name__, key)
+                        f"{self.__class__.__name__} got an unexpected keyword argument '{key}'"
                     )
 
         expr = self.visit(self._tree)
@@ -139,7 +137,7 @@ def __call__(self, *args, **kwargs) -> complex | ast.Expression | PulseExpressio
             if self._partial_binding:
                 return PulseExpression(expr, self._partial_binding)
             else:
-                raise PulseError("Parameters %s are not all bound." % self.params)
+                raise PulseError(f"Parameters {self.params} are not all bound.")
         return expr.body.value
 
     @staticmethod
@@ -160,7 +158,7 @@ def _match_ops(opr: ast.AST, opr_dict: dict, *args) -> complex:
         for op_type, op_func in opr_dict.items():
             if isinstance(opr, op_type):
                 return op_func(*args)
-        raise PulseError("Operator %s is not supported." % opr.__class__.__name__)
+        raise PulseError(f"Operator {opr.__class__.__name__} is not supported.")
 
     def visit_Expression(self, node: ast.Expression) -> ast.Expression:
         """Evaluate children nodes of expression.
@@ -273,7 +271,7 @@ def visit_Call(self, node: ast.Call) -> ast.Call | ast.Constant:
         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:
-                raise PulseError("Function %s is not supported." % node.func.id)
+                raise PulseError(f"Function {node.func.id} is not supported.")
             _args = [arg.value for arg in node.args]
             _val = self._math_ops[node.func.id](*_args)
             if not _val.imag:
@@ -283,7 +281,7 @@ def visit_Call(self, node: ast.Call) -> ast.Call | ast.Constant:
         return node
 
     def generic_visit(self, node):
-        raise PulseError("Unsupported node: %s" % node.__class__.__name__)
+        raise PulseError(f"Unsupported node: {node.__class__.__name__}")
 
 
 def parse_string_expr(source: str, partial_binding: bool = False) -> PulseExpression:
diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py
index 5241da0c31d1..7ccd5053e6e0 100644
--- a/qiskit/pulse/schedule.py
+++ b/qiskit/pulse/schedule.py
@@ -553,17 +553,10 @@ def _add_timeslots(self, time: int, schedule: "ScheduleComponent") -> None:
                     self._timeslots[channel].insert(index, interval)
                 except PulseError as ex:
                     raise PulseError(
-                        "Schedule(name='{new}') cannot be inserted into Schedule(name='{old}') at "
-                        "time {time} because its instruction on channel {ch} scheduled from time "
-                        "{t0} to {tf} overlaps with an existing instruction."
-                        "".format(
-                            new=schedule.name or "",
-                            old=self.name or "",
-                            time=time,
-                            ch=channel,
-                            t0=interval[0],
-                            tf=interval[1],
-                        )
+                        f"Schedule(name='{schedule.name or ''}') cannot be inserted into "
+                        f"Schedule(name='{self.name or ''}') at "
+                        f"time {time} because its instruction on channel {channel} scheduled from time "
+                        f"{interval[0]} to {interval[1]} overlaps with an existing instruction."
                     ) from ex
 
         _check_nonnegative_timeslot(self._timeslots)
@@ -598,10 +591,8 @@ def _remove_timeslots(self, time: int, schedule: "ScheduleComponent"):
                         continue
 
                 raise PulseError(
-                    "Cannot find interval ({t0}, {tf}) to remove from "
-                    "channel {ch} in Schedule(name='{name}').".format(
-                        ch=channel, t0=interval[0], tf=interval[1], name=schedule.name
-                    )
+                    f"Cannot find interval ({interval[0]}, {interval[1]}) to remove from "
+                    f"channel {channel} in Schedule(name='{schedule.name}')."
                 )
 
             if not channel_timeslots:
@@ -1615,8 +1606,9 @@ def __repr__(self) -> str:
         blocks = ", ".join([repr(instr) for instr in self.blocks[:50]])
         if len(self.blocks) > 25:
             blocks += ", ..."
-        return '{}({}, name="{}", transform={})'.format(
-            self.__class__.__name__, blocks, name, repr(self.alignment_context)
+        return (
+            f'{self.__class__.__name__}({blocks}, name="{name}",'
+            f" transform={repr(self.alignment_context)})"
         )
 
     def __add__(self, other: "BlockComponent") -> "ScheduleBlock":
diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py
index 569219777f20..5e383972c255 100644
--- a/qiskit/pulse/transforms/alignments.py
+++ b/qiskit/pulse/transforms/alignments.py
@@ -398,9 +398,7 @@ def align(self, schedule: Schedule) -> Schedule:
             _t_center = self.duration * self.func(ind + 1)
             _t0 = int(_t_center - 0.5 * child.duration)
             if _t0 < 0 or _t0 > self.duration:
-                raise PulseError(
-                    "Invalid schedule position t=%d is specified at index=%d" % (_t0, ind)
-                )
+                raise PulseError(f"Invalid schedule position t={_t0} is specified at index={ind}")
             aligned.insert(_t0, child, inplace=True)
 
         return aligned
diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py
index ae87fbafadde..5f345917761e 100644
--- a/qiskit/pulse/utils.py
+++ b/qiskit/pulse/utils.py
@@ -108,10 +108,9 @@ def instruction_duration_validation(duration: int):
     """
     if isinstance(duration, ParameterExpression):
         raise UnassignedDurationError(
-            "Instruction duration {} is not assigned. "
+            f"Instruction duration {repr(duration)} is not assigned. "
             "Please bind all durations to an integer value before playing in the Schedule, "
             "or use ScheduleBlock to align instructions with unassigned duration."
-            "".format(repr(duration))
         )
 
     if not isinstance(duration, (int, np.integer)) or duration < 0:
diff --git a/qiskit/qasm2/export.py b/qiskit/qasm2/export.py
index 9247c9233e09..46471fa087b2 100644
--- a/qiskit/qasm2/export.py
+++ b/qiskit/qasm2/export.py
@@ -157,7 +157,7 @@ def dumps(circuit: QuantumCircuit, /) -> str:
                 _make_unique(_escape_name(reg.name, "reg_"), register_escaped_names)
             ] = reg
     bit_labels: dict[Qubit | Clbit, str] = {
-        bit: "%s[%d]" % (name, idx)
+        bit: f"{name}[{idx}]"
         for name, register in register_escaped_names.items()
         for (idx, bit) in enumerate(register)
     }
@@ -244,18 +244,14 @@ def _instruction_call_site(operation):
     else:
         qasm2_call = operation.name
     if operation.params:
-        qasm2_call = "{}({})".format(
-            qasm2_call,
-            ",".join([pi_check(i, output="qasm", eps=1e-12) for i in operation.params]),
-        )
+        params = ",".join([pi_check(i, output="qasm", eps=1e-12) for i in operation.params])
+        qasm2_call = f"{qasm2_call}({params})"
     if operation.condition is not None:
         if not isinstance(operation.condition[0], ClassicalRegister):
             raise QASM2ExportError(
                 "OpenQASM 2 can only condition on registers, but got '{operation.condition[0]}'"
             )
-        qasm2_call = (
-            "if(%s==%d) " % (operation.condition[0].name, operation.condition[1]) + qasm2_call
-        )
+        qasm2_call = f"if({operation.condition[0].name}=={operation.condition[1]:d}) " + qasm2_call
     return qasm2_call
 
 
diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py
index 80c332aaab4a..11374e9aca92 100644
--- a/qiskit/qobj/converters/pulse_instruction.py
+++ b/qiskit/qobj/converters/pulse_instruction.py
@@ -621,7 +621,7 @@ def get_channel(self, channel: str) -> channels.PulseChannel:
             elif prefix == channels.ControlChannel.prefix:
                 return channels.ControlChannel(index)
 
-        raise QiskitError("Channel %s is not valid" % channel)
+        raise QiskitError(f"Channel {channel} is not valid")
 
     @staticmethod
     def disassemble_value(value_expr: Union[float, str]) -> Union[float, ParameterExpression]:
@@ -827,9 +827,7 @@ def _convert_parametric_pulse(
             pulse_name = instruction.label
         except AttributeError:
             sorted_params = sorted(instruction.parameters.items(), key=lambda x: x[0])
-            base_str = "{pulse}_{params}".format(
-                pulse=instruction.pulse_shape, params=str(sorted_params)
-            )
+            base_str = f"{instruction.pulse_shape}_{str(sorted_params)}"
             short_pulse_id = hashlib.md5(base_str.encode("utf-8")).hexdigest()[:4]
             pulse_name = f"{instruction.pulse_shape}_{short_pulse_id}"
         params = dict(instruction.parameters)
diff --git a/qiskit/qobj/pulse_qobj.py b/qiskit/qobj/pulse_qobj.py
index e5f45b4d2acb..3552d83ada84 100644
--- a/qiskit/qobj/pulse_qobj.py
+++ b/qiskit/qobj/pulse_qobj.py
@@ -209,8 +209,8 @@ def __repr__(self):
         return out
 
     def __str__(self):
-        out = "Instruction: %s\n" % self.name
-        out += "\t\tt0: %s\n" % self.t0
+        out = f"Instruction: {self.name}\n"
+        out += f"\t\tt0: {self.t0}\n"
         for attr in self._COMMON_ATTRS:
             if hasattr(self, attr):
                 out += f"\t\t{attr}: {getattr(self, attr)}\n"
@@ -434,10 +434,10 @@ def __str__(self):
             header = pprint.pformat(self.header.to_dict() or {})
         else:
             header = "{}"
-        out += "Header:\n%s\n" % header
-        out += "Config:\n%s\n\n" % config
+        out += f"Header:\n{header}\n"
+        out += f"Config:\n{config}\n\n"
         for instruction in self.instructions:
-            out += "\t%s\n" % instruction
+            out += f"\t{instruction}\n"
         return out
 
     @classmethod
@@ -567,23 +567,20 @@ def __init__(self, qobj_id, config, experiments, header=None):
     def __repr__(self):
         experiments_str = [repr(x) for x in self.experiments]
         experiments_repr = "[" + ", ".join(experiments_str) + "]"
-        out = "PulseQobj(qobj_id='{}', config={}, experiments={}, header={})".format(
-            self.qobj_id,
-            repr(self.config),
-            experiments_repr,
-            repr(self.header),
+        return (
+            f"PulseQobj(qobj_id='{self.qobj_id}', config={repr(self.config)}, "
+            f"experiments={experiments_repr}, header={repr(self.header)})"
         )
-        return out
 
     def __str__(self):
-        out = "Pulse Qobj: %s:\n" % self.qobj_id
+        out = f"Pulse Qobj: {self.qobj_id}:\n"
         config = pprint.pformat(self.config.to_dict())
-        out += "Config: %s\n" % str(config)
+        out += f"Config: {str(config)}\n"
         header = pprint.pformat(self.header.to_dict())
-        out += "Header: %s\n" % str(header)
+        out += f"Header: {str(header)}\n"
         out += "Experiments:\n"
         for experiment in self.experiments:
-            out += "%s" % str(experiment)
+            out += str(experiment)
         return out
 
     def to_dict(self):
diff --git a/qiskit/qobj/qasm_qobj.py b/qiskit/qobj/qasm_qobj.py
index 983da1dcfd3b..88d775b3b773 100644
--- a/qiskit/qobj/qasm_qobj.py
+++ b/qiskit/qobj/qasm_qobj.py
@@ -131,7 +131,7 @@ def to_dict(self):
         return out_dict
 
     def __repr__(self):
-        out = "QasmQobjInstruction(name='%s'" % self.name
+        out = f"QasmQobjInstruction(name='{self.name}'"
         for attr in [
             "params",
             "qubits",
@@ -155,7 +155,7 @@ def __repr__(self):
         return out
 
     def __str__(self):
-        out = "Instruction: %s\n" % self.name
+        out = f"Instruction: {self.name}\n"
         for attr in [
             "params",
             "qubits",
@@ -215,21 +215,19 @@ def __init__(self, config=None, header=None, instructions=None):
     def __repr__(self):
         instructions_str = [repr(x) for x in self.instructions]
         instructions_repr = "[" + ", ".join(instructions_str) + "]"
-        out = "QasmQobjExperiment(config={}, header={}, instructions={})".format(
-            repr(self.config),
-            repr(self.header),
-            instructions_repr,
+        return (
+            f"QasmQobjExperiment(config={repr(self.config)}, header={repr(self.header)},"
+            f" instructions={instructions_repr})"
         )
-        return out
 
     def __str__(self):
         out = "\nOpenQASM2 Experiment:\n"
         config = pprint.pformat(self.config.to_dict())
         header = pprint.pformat(self.header.to_dict())
-        out += "Header:\n%s\n" % header
-        out += "Config:\n%s\n\n" % config
+        out += f"Header:\n{header}\n"
+        out += f"Config:\n{config}\n\n"
         for instruction in self.instructions:
-            out += "\t%s\n" % instruction
+            out += f"\t{instruction}\n"
         return out
 
     def to_dict(self):
@@ -568,23 +566,20 @@ def __init__(self, qobj_id=None, config=None, experiments=None, header=None):
     def __repr__(self):
         experiments_str = [repr(x) for x in self.experiments]
         experiments_repr = "[" + ", ".join(experiments_str) + "]"
-        out = "QasmQobj(qobj_id='{}', config={}, experiments={}, header={})".format(
-            self.qobj_id,
-            repr(self.config),
-            experiments_repr,
-            repr(self.header),
+        return (
+            f"QasmQobj(qobj_id='{self.qobj_id}', config={repr(self.config)},"
+            f" experiments={experiments_repr}, header={repr(self.header)})"
         )
-        return out
 
     def __str__(self):
-        out = "QASM Qobj: %s:\n" % self.qobj_id
+        out = f"QASM Qobj: {self.qobj_id}:\n"
         config = pprint.pformat(self.config.to_dict())
-        out += "Config: %s\n" % str(config)
+        out += f"Config: {str(config)}\n"
         header = pprint.pformat(self.header.to_dict())
-        out += "Header: %s\n" % str(header)
+        out += f"Header: {str(header)}\n"
         out += "Experiments:\n"
         for experiment in self.experiments:
-            out += "%s" % str(experiment)
+            out += str(experiment)
         return out
 
     def to_dict(self):
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
index db53defbcfa7..0e2045d5be5d 100644
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -128,7 +128,7 @@ def _read_registers_v4(file_obj, num_registers):
             )
         )
         name = file_obj.read(data.name_size).decode("utf8")
-        REGISTER_ARRAY_PACK = "!%sq" % data.size
+        REGISTER_ARRAY_PACK = f"!{data.size}q"
         bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK))
         bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw))
         if data.type.decode("utf8") == "q":
@@ -148,7 +148,7 @@ def _read_registers(file_obj, num_registers):
             )
         )
         name = file_obj.read(data.name_size).decode("utf8")
-        REGISTER_ARRAY_PACK = "!%sI" % data.size
+        REGISTER_ARRAY_PACK = f"!{data.size}I"
         bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK))
         bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw))
         if data.type.decode("utf8") == "q":
@@ -352,7 +352,7 @@ def _read_instruction(
     elif gate_name == "Clifford":
         gate_class = Clifford
     else:
-        raise AttributeError("Invalid instruction type: %s" % gate_name)
+        raise AttributeError(f"Invalid instruction type: {gate_name}")
 
     if instruction.label_size <= 0:
         label = None
@@ -507,7 +507,7 @@ def _parse_custom_operation(
     if type_key == type_keys.CircuitInstruction.PAULI_EVOL_GATE:
         return definition
 
-    raise ValueError("Invalid custom instruction type '%s'" % type_str)
+    raise ValueError(f"Invalid custom instruction type '{type_str}'")
 
 
 def _read_pauli_evolution_gate(file_obj, version, vectors):
@@ -1031,7 +1031,7 @@ def _write_registers(file_obj, in_circ_regs, full_bits):
                 )
             )
             file_obj.write(reg_name)
-            REGISTER_ARRAY_PACK = "!%sq" % reg.size
+            REGISTER_ARRAY_PACK = f"!{reg.size}q"
             bit_indices = []
             for bit in reg:
                 bit_indices.append(bitmap.get(bit, -1))
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
index c9f0f9af4798..105d4364c07e 100644
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -277,7 +277,7 @@ def _read_parameter_expression(file_obj):
         elif elem_key == type_keys.Value.PARAMETER_EXPRESSION:
             value = common.data_from_binary(binary_data, _read_parameter_expression)
         else:
-            raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key)
+            raise exceptions.QpyError(f"Invalid parameter expression map type: {elem_key}")
         symbol_map[symbol] = value
 
     return ParameterExpression(symbol_map, expr_)
@@ -311,7 +311,7 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine):
         elif symbol_key == type_keys.Value.PARAMETER_VECTOR:
             symbol = _read_parameter_vec(file_obj, vectors)
         else:
-            raise exceptions.QpyError("Invalid parameter expression map type: %s" % symbol_key)
+            raise exceptions.QpyError(f"Invalid parameter expression map type: {symbol_key}")
 
         elem_key = type_keys.Value(elem_data.type)
         binary_data = file_obj.read(elem_data.size)
@@ -331,7 +331,7 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine):
                 use_symengine=use_symengine,
             )
         else:
-            raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key)
+            raise exceptions.QpyError(f"Invalid parameter expression map type: {elem_key}")
         symbol_map[symbol] = value
 
     return ParameterExpression(symbol_map, expr_)
diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py
index 34503dbab13b..d89117bc6a1c 100644
--- a/qiskit/qpy/interface.py
+++ b/qiskit/qpy/interface.py
@@ -304,10 +304,11 @@ def load(
     ):
         warnings.warn(
             "The qiskit version used to generate the provided QPY "
-            "file, %s, is newer than the current qiskit version %s. "
+            f"file, {'.'.join([str(x) for x in qiskit_version])}, "
+            f"is newer than the current qiskit version {__version__}. "
             "This may result in an error if the QPY file uses "
             "instructions not present in this current qiskit "
-            "version" % (".".join([str(x) for x in qiskit_version]), __version__)
+            "version"
         )
 
     if data.qpy_version < 5:
diff --git a/qiskit/quantum_info/operators/channel/quantum_channel.py b/qiskit/quantum_info/operators/channel/quantum_channel.py
index 16df920e2e00..ff20feb5bf4f 100644
--- a/qiskit/quantum_info/operators/channel/quantum_channel.py
+++ b/qiskit/quantum_info/operators/channel/quantum_channel.py
@@ -66,12 +66,9 @@ def __init__(
     def __repr__(self):
         prefix = f"{self._channel_rep}("
         pad = len(prefix) * " "
-        return "{}{},\n{}input_dims={}, output_dims={})".format(
-            prefix,
-            np.array2string(np.asarray(self.data), separator=", ", prefix=prefix),
-            pad,
-            self.input_dims(),
-            self.output_dims(),
+        return (
+            f"{prefix}{np.array2string(np.asarray(self.data), separator=', ', prefix=prefix)}"
+            f",\n{pad}input_dims={self.input_dims()}, output_dims={self.output_dims()})"
         )
 
     def __eq__(self, other: Self):
diff --git a/qiskit/quantum_info/operators/channel/superop.py b/qiskit/quantum_info/operators/channel/superop.py
index 19867696ec6a..f07652e22d79 100644
--- a/qiskit/quantum_info/operators/channel/superop.py
+++ b/qiskit/quantum_info/operators/channel/superop.py
@@ -355,8 +355,8 @@ def _append_instruction(self, obj, qargs=None):
                 raise QiskitError(f"Cannot apply Instruction: {obj.name}")
             if not isinstance(obj.definition, QuantumCircuit):
                 raise QiskitError(
-                    "{} instruction definition is {}; "
-                    "expected QuantumCircuit".format(obj.name, type(obj.definition))
+                    f"{obj.name} instruction definition is {type(obj.definition)}; "
+                    "expected QuantumCircuit"
                 )
             qubit_indices = {bit: idx for idx, bit in enumerate(obj.definition.qubits)}
             for instruction in obj.definition.data:
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
index 7104dced9df5..bfe76a2f3ca9 100644
--- a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
@@ -92,9 +92,7 @@ def _append_circuit(elem, circuit, qargs=None):
         raise QiskitError(f"Cannot apply Instruction: {gate.name}")
     if not isinstance(gate.definition, QuantumCircuit):
         raise QiskitError(
-            "{} instruction definition is {}; expected QuantumCircuit".format(
-                gate.name, type(gate.definition)
-            )
+            f"{gate.name} instruction definition is {type(gate.definition)}; expected QuantumCircuit"
         )
 
     flat_instr = gate.definition
diff --git a/qiskit/quantum_info/operators/measures.py b/qiskit/quantum_info/operators/measures.py
index 617e9f64b687..293c1236ed70 100644
--- a/qiskit/quantum_info/operators/measures.py
+++ b/qiskit/quantum_info/operators/measures.py
@@ -93,7 +93,7 @@ def process_fidelity(
         if channel.dim != target.dim:
             raise QiskitError(
                 "Input quantum channel and target unitary must have the same "
-                "dimensions ({} != {}).".format(channel.dim, target.dim)
+                f"dimensions ({channel.dim} != {target.dim})."
             )
 
     # Validate complete-positivity and trace-preserving
diff --git a/qiskit/quantum_info/operators/op_shape.py b/qiskit/quantum_info/operators/op_shape.py
index 4f95126ea148..42f05a8c53ab 100644
--- a/qiskit/quantum_info/operators/op_shape.py
+++ b/qiskit/quantum_info/operators/op_shape.py
@@ -193,7 +193,7 @@ def _validate(self, shape, raise_exception=False):
                 if raise_exception:
                     raise QiskitError(
                         "Output dimensions do not match matrix shape "
-                        "({} != {})".format(reduce(mul, self._dims_l), shape[0])
+                        f"({reduce(mul, self._dims_l)} != {shape[0]})"
                     )
                 return False
         elif shape[0] != 2**self._num_qargs_l:
@@ -207,7 +207,7 @@ def _validate(self, shape, raise_exception=False):
                     if raise_exception:
                         raise QiskitError(
                             "Input dimensions do not match matrix shape "
-                            "({} != {})".format(reduce(mul, self._dims_r), shape[1])
+                            f"({reduce(mul, self._dims_r)} != {shape[1]})"
                         )
                     return False
             elif shape[1] != 2**self._num_qargs_r:
@@ -430,7 +430,7 @@ def compose(self, other, qargs=None, front=False):
                 if self._num_qargs_r != other._num_qargs_l or self._dims_r != other._dims_l:
                     raise QiskitError(
                         "Left and right compose dimensions don't match "
-                        "({} != {})".format(self.dims_r(), other.dims_l())
+                        f"({self.dims_r()} != {other.dims_l()})"
                     )
                 ret._dims_l = self._dims_l
                 ret._dims_r = other._dims_r
@@ -440,7 +440,7 @@ def compose(self, other, qargs=None, front=False):
                 if self._num_qargs_l != other._num_qargs_r or self._dims_l != other._dims_r:
                     raise QiskitError(
                         "Left and right compose dimensions don't match "
-                        "({} != {})".format(self.dims_l(), other.dims_r())
+                        f"({self.dims_l()} != {other.dims_r()})"
                     )
                 ret._dims_l = other._dims_l
                 ret._dims_r = self._dims_r
@@ -453,15 +453,13 @@ def compose(self, other, qargs=None, front=False):
             ret._num_qargs_l = self._num_qargs_l
             if len(qargs) != other._num_qargs_l:
                 raise QiskitError(
-                    "Number of qargs does not match ({} != {})".format(
-                        len(qargs), other._num_qargs_l
-                    )
+                    f"Number of qargs does not match ({len(qargs)} != {other._num_qargs_l})"
                 )
             if self._dims_r or other._dims_r:
                 if self.dims_r(qargs) != other.dims_l():
                     raise QiskitError(
                         "Subsystem dimension do not match on specified qargs "
-                        "{} != {}".format(self.dims_r(qargs), other.dims_l())
+                        f"{self.dims_r(qargs)} != {other.dims_l()}"
                     )
                 dims_r = list(self.dims_r())
                 for i, dim in zip(qargs, other.dims_r()):
@@ -475,15 +473,13 @@ def compose(self, other, qargs=None, front=False):
             ret._num_qargs_r = self._num_qargs_r
             if len(qargs) != other._num_qargs_r:
                 raise QiskitError(
-                    "Number of qargs does not match ({} != {})".format(
-                        len(qargs), other._num_qargs_r
-                    )
+                    f"Number of qargs does not match ({len(qargs)} != {other._num_qargs_r})"
                 )
             if self._dims_l or other._dims_l:
                 if self.dims_l(qargs) != other.dims_r():
                     raise QiskitError(
                         "Subsystem dimension do not match on specified qargs "
-                        "{} != {}".format(self.dims_l(qargs), other.dims_r())
+                        f"{self.dims_l(qargs)} != {other.dims_r()}"
                     )
                 dims_l = list(self.dims_l())
                 for i, dim in zip(qargs, other.dims_l()):
@@ -508,26 +504,22 @@ def _validate_add(self, other, qargs=None):
             if self.dims_l(qargs) != other.dims_l():
                 raise QiskitError(
                     "Cannot add shapes width different left "
-                    "dimension on specified qargs {} != {}".format(
-                        self.dims_l(qargs), other.dims_l()
-                    )
+                    f"dimension on specified qargs {self.dims_l(qargs)} != {other.dims_l()}"
                 )
             if self.dims_r(qargs) != other.dims_r():
                 raise QiskitError(
                     "Cannot add shapes width different total right "
-                    "dimension on specified qargs{} != {}".format(
-                        self.dims_r(qargs), other.dims_r()
-                    )
+                    f"dimension on specified qargs{self.dims_r(qargs)} != {other.dims_r()}"
                 )
         elif self != other:
             if self._dim_l != other._dim_l:
                 raise QiskitError(
                     "Cannot add shapes width different total left "
-                    "dimension {} != {}".format(self._dim_l, other._dim_l)
+                    f"dimension {self._dim_l} != {other._dim_l}"
                 )
             if self._dim_r != other._dim_r:
                 raise QiskitError(
                     "Cannot add shapes width different total right "
-                    "dimension {} != {}".format(self._dim_r, other._dim_r)
+                    f"dimension {self._dim_r} != {other._dim_r}"
                 )
         return self
diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
index 016e337f082c..a4e93f364809 100644
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -128,12 +128,9 @@ def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
     def __repr__(self):
         prefix = "Operator("
         pad = len(prefix) * " "
-        return "{}{},\n{}input_dims={}, output_dims={})".format(
-            prefix,
-            np.array2string(self.data, separator=", ", prefix=prefix),
-            pad,
-            self.input_dims(),
-            self.output_dims(),
+        return (
+            f"{prefix}{np.array2string(self.data, separator=', ', prefix=prefix)},\n"
+            f"{pad}input_dims={self.input_dims()}, output_dims={self.output_dims()})"
         )
 
     def __eq__(self, other):
@@ -763,10 +760,8 @@ def _append_instruction(self, obj, qargs=None):
                 raise QiskitError(f"Cannot apply Operation: {obj.name}")
             if not isinstance(obj.definition, QuantumCircuit):
                 raise QiskitError(
-                    'Operation "{}" '
-                    "definition is {} but expected QuantumCircuit.".format(
-                        obj.name, type(obj.definition)
-                    )
+                    f'Operation "{obj.name}" '
+                    f"definition is {type(obj.definition)} but expected QuantumCircuit."
                 )
             if obj.definition.global_phase:
                 dimension = 2**obj.num_qubits
diff --git a/qiskit/quantum_info/operators/predicates.py b/qiskit/quantum_info/operators/predicates.py
index 57b7df64f268..f432195cd571 100644
--- a/qiskit/quantum_info/operators/predicates.py
+++ b/qiskit/quantum_info/operators/predicates.py
@@ -22,6 +22,7 @@
 
 
 def matrix_equal(mat1, mat2, ignore_phase=False, rtol=RTOL_DEFAULT, atol=ATOL_DEFAULT, props=None):
+    # pylint: disable-next=consider-using-f-string
     """Test if two arrays are equal.
 
     The final comparison is implemented using Numpy.allclose. See its
diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py
index e43eca4aff21..38e471f0b0a6 100644
--- a/qiskit/quantum_info/operators/symplectic/base_pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py
@@ -215,12 +215,12 @@ def commutes(self, other: BasePauli, qargs: list | None = None) -> np.ndarray:
         if qargs is not None and len(qargs) != other.num_qubits:
             raise QiskitError(
                 "Number of qubits of other Pauli does not match number of "
-                "qargs ({} != {}).".format(other.num_qubits, len(qargs))
+                f"qargs ({other.num_qubits} != {len(qargs)})."
             )
         if qargs is None and self.num_qubits != other.num_qubits:
             raise QiskitError(
                 "Number of qubits of other Pauli does not match the current "
-                "Pauli ({} != {}).".format(other.num_qubits, self.num_qubits)
+                f"Pauli ({other.num_qubits} != {self.num_qubits})."
             )
         if qargs is not None:
             inds = list(qargs)
@@ -262,15 +262,12 @@ def evolve(
         # Check dimension
         if qargs is not None and len(qargs) != other.num_qubits:
             raise QiskitError(
-                "Incorrect number of qubits for Clifford circuit ({} != {}).".format(
-                    other.num_qubits, len(qargs)
-                )
+                f"Incorrect number of qubits for Clifford circuit ({other.num_qubits} != {len(qargs)})."
             )
         if qargs is None and self.num_qubits != other.num_qubits:
             raise QiskitError(
-                "Incorrect number of qubits for Clifford circuit ({} != {}).".format(
-                    other.num_qubits, self.num_qubits
-                )
+                f"Incorrect number of qubits for Clifford circuit "
+                f"({other.num_qubits} != {self.num_qubits})."
             )
 
         # Evolve via Pauli
@@ -571,9 +568,8 @@ def _append_circuit(self, circuit, qargs=None):
                 raise QiskitError(f"Cannot apply Instruction: {gate.name}")
             if not isinstance(gate.definition, QuantumCircuit):
                 raise QiskitError(
-                    "{} instruction definition is {}; expected QuantumCircuit".format(
-                        gate.name, type(gate.definition)
-                    )
+                    f"{gate.name} instruction definition is {type(gate.definition)};"
+                    f" expected QuantumCircuit"
                 )
 
             circuit = gate.definition
diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index 8187c1ee41e6..867867eeb98a 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli.py
@@ -344,7 +344,7 @@ def delete(self, qubits: int | list) -> Pauli:
         if max(qubits) > self.num_qubits - 1:
             raise QiskitError(
                 "Qubit index is larger than the number of qubits "
-                "({}>{}).".format(max(qubits), self.num_qubits - 1)
+                f"({max(qubits)}>{self.num_qubits - 1})."
             )
         if len(qubits) == self.num_qubits:
             raise QiskitError("Cannot delete all qubits of Pauli")
@@ -379,12 +379,12 @@ def insert(self, qubits: int | list, value: Pauli) -> Pauli:
         if len(qubits) != value.num_qubits:
             raise QiskitError(
                 "Number of indices does not match number of qubits for "
-                "the inserted Pauli ({}!={})".format(len(qubits), value.num_qubits)
+                f"the inserted Pauli ({len(qubits)}!={value.num_qubits})"
             )
         if max(qubits) > ret.num_qubits - 1:
             raise QiskitError(
                 "Index is too larger for combined Pauli number of qubits "
-                "({}>{}).".format(max(qubits), ret.num_qubits - 1)
+                f"({max(qubits)}>{ret.num_qubits - 1})."
             )
         # Qubit positions for original op
         self_qubits = [i for i in range(ret.num_qubits) if i not in qubits]
diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py
index 3d348d236387..f2e408dd9bd2 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli_list.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py
@@ -382,8 +382,8 @@ def delete(self, ind: int | list, qubit: bool = False) -> PauliList:
         if not qubit:
             if max(ind) >= len(self):
                 raise QiskitError(
-                    "Indices {} are not all less than the size"
-                    " of the PauliList ({})".format(ind, len(self))
+                    f"Indices {ind} are not all less than the size"
+                    f" of the PauliList ({len(self)})"
                 )
             z = np.delete(self._z, ind, axis=0)
             x = np.delete(self._x, ind, axis=0)
@@ -394,8 +394,8 @@ def delete(self, ind: int | list, qubit: bool = False) -> PauliList:
         # Column (qubit) deletion
         if max(ind) >= self.num_qubits:
             raise QiskitError(
-                "Indices {} are not all less than the number of"
-                " qubits in the PauliList ({})".format(ind, self.num_qubits)
+                f"Indices {ind} are not all less than the number of"
+                f" qubits in the PauliList ({self.num_qubits})"
             )
         z = np.delete(self._z, ind, axis=1)
         x = np.delete(self._x, ind, axis=1)
@@ -432,8 +432,7 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList:
         if not qubit:
             if ind > size:
                 raise QiskitError(
-                    "Index {} is larger than the number of rows in the"
-                    " PauliList ({}).".format(ind, size)
+                    f"Index {ind} is larger than the number of rows in the" f" PauliList ({size})."
                 )
             base_z = np.insert(self._z, ind, value._z, axis=0)
             base_x = np.insert(self._x, ind, value._x, axis=0)
@@ -443,8 +442,8 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList:
         # Column insertion
         if ind > self.num_qubits:
             raise QiskitError(
-                "Index {} is greater than number of qubits"
-                " in the PauliList ({})".format(ind, self.num_qubits)
+                f"Index {ind} is greater than number of qubits"
+                f" in the PauliList ({self.num_qubits})"
             )
         if len(value) == 1:
             # Pad blocks to correct size
@@ -461,7 +460,7 @@ def insert(self, ind: int, value: PauliList, qubit: bool = False) -> PauliList:
             raise QiskitError(
                 "Input PauliList must have a single row, or"
                 " the same number of rows as the Pauli Table"
-                " ({}).".format(size)
+                f" ({size})."
             )
         # Build new array by blocks
         z = np.hstack([self.z[:, :ind], value_z, self.z[:, ind:]])
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index 6204189be443..91d8d82decae 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -172,7 +172,7 @@ def __init__(
         if self._coeffs.shape != (self._pauli_list.size,):
             raise QiskitError(
                 "coeff vector is incorrect shape for number"
-                " of Paulis {} != {}".format(self._coeffs.shape, self._pauli_list.size)
+                f" of Paulis {self._coeffs.shape} != {self._pauli_list.size}"
             )
         # Initialize LinearOp
         super().__init__(num_qubits=self._pauli_list.num_qubits)
@@ -186,11 +186,9 @@ def __array__(self, dtype=None, copy=None):
     def __repr__(self):
         prefix = "SparsePauliOp("
         pad = len(prefix) * " "
-        return "{}{},\n{}coeffs={})".format(
-            prefix,
-            self.paulis.to_labels(),
-            pad,
-            np.array2string(self.coeffs, separator=", "),
+        return (
+            f"{prefix}{self.paulis.to_labels()},\n{pad}"
+            f"coeffs={np.array2string(self.coeffs, separator=', ')})"
         )
 
     def __eq__(self, other):
diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py
index 1c66d8bcf5cf..7826e3f22fec 100644
--- a/qiskit/quantum_info/states/densitymatrix.py
+++ b/qiskit/quantum_info/states/densitymatrix.py
@@ -123,11 +123,9 @@ def __eq__(self, other):
     def __repr__(self):
         prefix = "DensityMatrix("
         pad = len(prefix) * " "
-        return "{}{},\n{}dims={})".format(
-            prefix,
-            np.array2string(self._data, separator=", ", prefix=prefix),
-            pad,
-            self._op_shape.dims_l(),
+        return (
+            f"{prefix}{np.array2string(self._data, separator=', ', prefix=prefix)},\n"
+            f"{pad}dims={self._op_shape.dims_l()})"
         )
 
     @property
@@ -771,9 +769,8 @@ def _append_instruction(self, other, qargs=None):
             raise QiskitError(f"Cannot apply Instruction: {other.name}")
         if not isinstance(other.definition, QuantumCircuit):
             raise QiskitError(
-                "{} instruction definition is {}; expected QuantumCircuit".format(
-                    other.name, type(other.definition)
-                )
+                f"{other.name} instruction definition is {type(other.definition)};"
+                f" expected QuantumCircuit"
             )
         qubit_indices = {bit: idx for idx, bit in enumerate(other.definition.qubits)}
         for instruction in other.definition:
diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py
index df39ba42f915..7fa14eaac9da 100644
--- a/qiskit/quantum_info/states/statevector.py
+++ b/qiskit/quantum_info/states/statevector.py
@@ -117,11 +117,9 @@ def __eq__(self, other):
     def __repr__(self):
         prefix = "Statevector("
         pad = len(prefix) * " "
-        return "{}{},\n{}dims={})".format(
-            prefix,
-            np.array2string(self._data, separator=", ", prefix=prefix),
-            pad,
-            self._op_shape.dims_l(),
+        return (
+            f"{prefix}{np.array2string(self._data, separator=', ', prefix=prefix)},\n{pad}"
+            f"dims={self._op_shape.dims_l()})"
         )
 
     @property
@@ -940,9 +938,7 @@ def _evolve_instruction(statevec, obj, qargs=None):
             raise QiskitError(f"Cannot apply Instruction: {obj.name}")
         if not isinstance(obj.definition, QuantumCircuit):
             raise QiskitError(
-                "{} instruction definition is {}; expected QuantumCircuit".format(
-                    obj.name, type(obj.definition)
-                )
+                f"{obj.name} instruction definition is {type(obj.definition)}; expected QuantumCircuit"
             )
 
         if obj.definition.global_phase:
diff --git a/qiskit/result/counts.py b/qiskit/result/counts.py
index 8b90ff0f0428..8168a3d21900 100644
--- a/qiskit/result/counts.py
+++ b/qiskit/result/counts.py
@@ -130,7 +130,7 @@ def most_frequent(self):
         max_values_counts = [x[0] for x in self.items() if x[1] == max_value]
         if len(max_values_counts) != 1:
             raise exceptions.QiskitError(
-                "Multiple values have the same maximum counts: %s" % ",".join(max_values_counts)
+                f"Multiple values have the same maximum counts: {','.join(max_values_counts)}"
             )
         return max_values_counts[0]
 
diff --git a/qiskit/result/mitigation/correlated_readout_mitigator.py b/qiskit/result/mitigation/correlated_readout_mitigator.py
index 06cc89b4c52b..99e6f9ae4145 100644
--- a/qiskit/result/mitigation/correlated_readout_mitigator.py
+++ b/qiskit/result/mitigation/correlated_readout_mitigator.py
@@ -54,8 +54,8 @@ def __init__(self, assignment_matrix: np.ndarray, qubits: Optional[Iterable[int]
         else:
             if len(qubits) != matrix_qubits_num:
                 raise QiskitError(
-                    "The number of given qubits ({}) is different than the number of "
-                    "qubits inferred from the matrices ({})".format(len(qubits), matrix_qubits_num)
+                    f"The number of given qubits ({len(qubits)}) is different than the number of "
+                    f"qubits inferred from the matrices ({matrix_qubits_num})"
                 )
             self._qubits = qubits
             self._num_qubits = len(self._qubits)
diff --git a/qiskit/result/mitigation/local_readout_mitigator.py b/qiskit/result/mitigation/local_readout_mitigator.py
index ad71911c2d75..197c3f00d9be 100644
--- a/qiskit/result/mitigation/local_readout_mitigator.py
+++ b/qiskit/result/mitigation/local_readout_mitigator.py
@@ -68,8 +68,8 @@ def __init__(
         else:
             if len(qubits) != len(assignment_matrices):
                 raise QiskitError(
-                    "The number of given qubits ({}) is different than the number of qubits "
-                    "inferred from the matrices ({})".format(len(qubits), len(assignment_matrices))
+                    f"The number of given qubits ({len(qubits)}) is different than the number of qubits "
+                    f"inferred from the matrices ({len(assignment_matrices)})"
                 )
             self._qubits = qubits
             self._num_qubits = len(self._qubits)
diff --git a/qiskit/result/mitigation/utils.py b/qiskit/result/mitigation/utils.py
index 26b5ee373488..823e2b69a6ca 100644
--- a/qiskit/result/mitigation/utils.py
+++ b/qiskit/result/mitigation/utils.py
@@ -120,9 +120,7 @@ def marganalize_counts(
         clbits_len = len(clbits) if not clbits is None else 0
         if clbits_len not in (0, qubits_len):
             raise QiskitError(
-                "Num qubits ({}) does not match number of clbits ({}).".format(
-                    qubits_len, clbits_len
-                )
+                f"Num qubits ({qubits_len}) does not match number of clbits ({clbits_len})."
             )
         counts = marginal_counts(counts, clbits)
     if clbits is None and qubits is not None:
diff --git a/qiskit/result/models.py b/qiskit/result/models.py
index 992810196713..83d9e4e78d5f 100644
--- a/qiskit/result/models.py
+++ b/qiskit/result/models.py
@@ -66,8 +66,7 @@ def __repr__(self):
         string_list = []
         for field in self._data_attributes:
             string_list.append(f"{field}={getattr(self, field)}")
-        out = "ExperimentResultData(%s)" % ", ".join(string_list)
-        return out
+        return f"ExperimentResultData({', '.join(string_list)})"
 
     def to_dict(self):
         """Return a dictionary format representation of the ExperimentResultData
@@ -157,23 +156,21 @@ def __init__(
         self._metadata.update(kwargs)
 
     def __repr__(self):
-        out = "ExperimentResult(shots={}, success={}, meas_level={}, data={}".format(
-            self.shots,
-            self.success,
-            self.meas_level,
-            self.data,
+        out = (
+            f"ExperimentResult(shots={self.shots}, success={self.success},"
+            f" meas_level={self.meas_level}, data={self.data}"
         )
         if hasattr(self, "header"):
-            out += ", header=%s" % self.header
+            out += f", header={self.header}"
         if hasattr(self, "status"):
-            out += ", status=%s" % self.status
+            out += f", status={self.status}"
         if hasattr(self, "seed"):
-            out += ", seed=%s" % self.seed
+            out += f", seed={self.seed}"
         if hasattr(self, "meas_return"):
-            out += ", meas_return=%s" % self.meas_return
+            out += f", meas_return={self.meas_return}"
         for key, value in self._metadata.items():
             if isinstance(value, str):
-                value_str = "'%s'" % value
+                value_str = f"'{value}'"
             else:
                 value_str = repr(value)
             out += f", {key}={value_str}"
diff --git a/qiskit/result/result.py b/qiskit/result/result.py
index c1792de56ae0..7df365785166 100644
--- a/qiskit/result/result.py
+++ b/qiskit/result/result.py
@@ -69,21 +69,14 @@ def __init__(
 
     def __repr__(self):
         out = (
-            "Result(backend_name='%s', backend_version='%s', qobj_id='%s', "
-            "job_id='%s', success=%s, results=%s"
-            % (
-                self.backend_name,
-                self.backend_version,
-                self.qobj_id,
-                self.job_id,
-                self.success,
-                self.results,
-            )
+            f"Result(backend_name='{self.backend_name}', backend_version='{self.backend_version}',"
+            f" qobj_id='{self.qobj_id}', job_id='{self.job_id}', success={self.success},"
+            f" results={self.results}"
         )
         out += f", date={self.date}, status={self.status}, header={self.header}"
         for key, value in self._metadata.items():
             if isinstance(value, str):
-                value_str = "'%s'" % value
+                value_str = f"'{value}'"
             else:
                 value_str = repr(value)
             out += f", {key}={value_str}"
@@ -236,10 +229,10 @@ def get_memory(self, experiment=None):
 
         except KeyError as ex:
             raise QiskitError(
-                'No memory for experiment "{}". '
+                f'No memory for experiment "{repr(experiment)}". '
                 "Please verify that you either ran a measurement level 2 job "
                 'with the memory flag set, eg., "memory=True", '
-                "or a measurement level 0/1 job.".format(repr(experiment))
+                "or a measurement level 0/1 job."
             ) from ex
 
     def get_counts(self, experiment=None):
@@ -377,14 +370,14 @@ def _get_experiment(self, key=None):
             ]
 
             if len(exp) == 0:
-                raise QiskitError('Data for experiment "%s" could not be found.' % key)
+                raise QiskitError(f'Data for experiment "{key}" could not be found.')
             if len(exp) == 1:
                 exp = exp[0]
             else:
                 warnings.warn(
-                    'Result object contained multiple results matching name "%s", '
+                    f'Result object contained multiple results matching name "{key}", '
                     "only first match will be returned. Use an integer index to "
-                    "retrieve results for all entries." % key
+                    "retrieve results for all entries."
                 )
                 exp = exp[0]
 
diff --git a/qiskit/scheduler/lowering.py b/qiskit/scheduler/lowering.py
index fa622b205d3b..f0fb33957d9e 100644
--- a/qiskit/scheduler/lowering.py
+++ b/qiskit/scheduler/lowering.py
@@ -145,9 +145,9 @@ def get_measure_schedule(qubit_mem_slots: Dict[int, int]) -> CircuitPulseDef:
         elif isinstance(instruction.operation, Measure):
             if len(inst_qubits) != 1 and len(instruction.clbits) != 1:
                 raise QiskitError(
-                    "Qubit '{}' or classical bit '{}' errored because the "
+                    f"Qubit '{inst_qubits}' or classical bit '{instruction.clbits}' errored because the "
                     "circuit Measure instruction only takes one of "
-                    "each.".format(inst_qubits, instruction.clbits)
+                    "each."
                 )
             qubit_mem_slots[inst_qubits[0]] = clbit_indices[instruction.clbits[0]]
         else:
diff --git a/qiskit/synthesis/linear/cnot_synth.py b/qiskit/synthesis/linear/cnot_synth.py
index 5063577ed653..699523a7e75d 100644
--- a/qiskit/synthesis/linear/cnot_synth.py
+++ b/qiskit/synthesis/linear/cnot_synth.py
@@ -53,8 +53,7 @@ def synth_cnot_count_full_pmh(
     """
     if not isinstance(state, (list, np.ndarray)):
         raise QiskitError(
-            "state should be of type list or numpy.ndarray, "
-            "but was of the type {}".format(type(state))
+            f"state should be of type list or numpy.ndarray, but was of the type {type(state)}"
         )
     state = np.array(state)
     # Synthesize lower triangular part
diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py
index 41ba75c6b237..26a5b52521b2 100644
--- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py
+++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py
@@ -116,7 +116,7 @@ def decompose_two_qubit_product_gate(special_unitary_matrix: np.ndarray):
     if deviation > 1.0e-13:
         raise QiskitError(
             "decompose_two_qubit_product_gate: decomposition failed: "
-            "deviation too large: {}".format(deviation)
+            f"deviation too large: {deviation}"
         )
 
     return L, R, phase
diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py
index 614e166050e8..27f98da68e7c 100644
--- a/qiskit/transpiler/coupling.py
+++ b/qiskit/transpiler/coupling.py
@@ -101,7 +101,7 @@ def add_physical_qubit(self, physical_qubit):
             raise CouplingError("Physical qubits should be integers.")
         if physical_qubit in self.physical_qubits:
             raise CouplingError(
-                "The physical qubit %s is already in the coupling graph" % physical_qubit
+                f"The physical qubit {physical_qubit} is already in the coupling graph"
             )
         self.graph.add_node(physical_qubit)
         self._dist_matrix = None  # invalidate
@@ -188,9 +188,9 @@ def distance(self, physical_qubit1, physical_qubit2):
             CouplingError: if the qubits do not exist in the CouplingMap
         """
         if physical_qubit1 >= self.size():
-            raise CouplingError("%s not in coupling graph" % physical_qubit1)
+            raise CouplingError(f"{physical_qubit1} not in coupling graph")
         if physical_qubit2 >= self.size():
-            raise CouplingError("%s not in coupling graph" % physical_qubit2)
+            raise CouplingError(f"{physical_qubit2} not in coupling graph")
         self.compute_distance_matrix()
         res = self._dist_matrix[physical_qubit1, physical_qubit2]
         if res == math.inf:
diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py
index 1bebc7b84dc8..4117e2987bb6 100644
--- a/qiskit/transpiler/layout.py
+++ b/qiskit/transpiler/layout.py
@@ -98,8 +98,8 @@ def order_based_on_type(value1, value2):
             virtual = value1
         else:
             raise LayoutError(
-                "The map (%s -> %s) has to be a (Bit -> integer)"
-                " or the other way around." % (type(value1), type(value2))
+                f"The map ({type(value1)} -> {type(value2)}) has to be a (Bit -> integer)"
+                " or the other way around."
             )
         return virtual, physical
 
@@ -137,7 +137,7 @@ def __delitem__(self, key):
         else:
             raise LayoutError(
                 "The key to remove should be of the form"
-                " Qubit or integer) and %s was provided" % (type(key),)
+                f" Qubit or integer) and {type(key)} was provided"
             )
 
     def __len__(self):
diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py
index bcac1f5d4faf..936613744b84 100644
--- a/qiskit/transpiler/passes/basis/basis_translator.py
+++ b/qiskit/transpiler/passes/basis/basis_translator.py
@@ -302,9 +302,7 @@ def _replace_node(self, dag, node, instr_map):
         if len(node.op.params) != len(target_params):
             raise TranspilerError(
                 "Translation num_params not equal to op num_params."
-                "Op: {} {} Translation: {}\n{}".format(
-                    node.op.params, node.op.name, target_params, target_dag
-                )
+                f"Op: {node.op.params} {node.op.name} Translation: {target_params}\n{target_dag}"
             )
         if node.op.params:
             parameter_map = dict(zip(target_params, node.op.params))
diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py
index 701e87dd9cd5..73e1d4ac5484 100644
--- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py
+++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py
@@ -78,7 +78,7 @@ def run(self, dag):
                     continue
                 raise QiskitError(
                     "Cannot unroll all 3q or more gates. "
-                    "No rule to expand instruction %s." % node.op.name
+                    f"No rule to expand instruction {node.op.name}."
                 )
             decomposition = circuit_to_dag(node.op.definition, copy_operations=False)
             decomposition = self.run(decomposition)  # recursively unroll
diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
index a54e4bfcb001..99bf95147ae2 100644
--- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
+++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py
@@ -95,9 +95,9 @@ def run(self, dag):
             if unrolled is None:
                 # opaque node
                 raise QiskitError(
-                    "Cannot unroll the circuit to the given basis, %s. "
-                    "Instruction %s not found in equivalence library "
-                    "and no rule found to expand." % (str(self._basis_gates), node.op.name)
+                    f"Cannot unroll the circuit to the given basis, {str(self._basis_gates)}. "
+                    f"Instruction {node.op.name} not found in equivalence library "
+                    "and no rule found to expand."
                 )
 
             decomposition = circuit_to_dag(unrolled, copy_operations=False)
diff --git a/qiskit/transpiler/passes/calibration/rzx_builder.py b/qiskit/transpiler/passes/calibration/rzx_builder.py
index 4cb576a23bc9..c153c3eeef33 100644
--- a/qiskit/transpiler/passes/calibration/rzx_builder.py
+++ b/qiskit/transpiler/passes/calibration/rzx_builder.py
@@ -204,7 +204,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche
         if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]:
             xgate = self._inst_map.get("x", qubits[0])
             with builder.build(
-                default_alignment="sequential", name="rzx(%.3f)" % theta
+                default_alignment="sequential", name=f"rzx({theta:.3f})"
             ) as rzx_theta_native:
                 for cr_tone, comp_tone in zip(cr_tones, comp_tones):
                     with builder.align_left():
@@ -230,7 +230,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche
             builder.call(szt, name="szt")
 
         with builder.build(
-            default_alignment="sequential", name="rzx(%.3f)" % theta
+            default_alignment="sequential", name=f"rzx({theta:.3f})"
         ) as rzx_theta_flip:
             builder.call(hadamard, name="hadamard")
             for cr_tone, comp_tone in zip(cr_tones, comp_tones):
@@ -297,7 +297,7 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche
 
         # RZXCalibrationNoEcho only good for forward CR direction
         if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]:
-            with builder.build(default_alignment="left", name="rzx(%.3f)" % theta) as rzx_theta:
+            with builder.build(default_alignment="left", name=f"rzx({theta:.3f})") as rzx_theta:
                 stretched_dur = self.rescale_cr_inst(cr_tones[0], 2 * theta)
                 self.rescale_cr_inst(comp_tones[0], 2 * theta)
                 # Placeholder to make pulse gate work
diff --git a/qiskit/transpiler/passes/optimization/inverse_cancellation.py b/qiskit/transpiler/passes/optimization/inverse_cancellation.py
index 958f53ef057d..f5523432c26e 100644
--- a/qiskit/transpiler/passes/optimization/inverse_cancellation.py
+++ b/qiskit/transpiler/passes/optimization/inverse_cancellation.py
@@ -53,8 +53,8 @@ def __init__(self, gates_to_cancel: List[Union[Gate, Tuple[Gate, Gate]]]):
                     )
             else:
                 raise TranspilerError(
-                    "InverseCancellation pass does not take input type {}. Input must be"
-                    " a Gate.".format(type(gates))
+                    f"InverseCancellation pass does not take input type {type(gates)}. Input must be"
+                    " a Gate."
                 )
 
         self.self_inverse_gates = []
diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py
index 9370fe7409f0..f8302b9232c0 100644
--- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py
+++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py
@@ -308,7 +308,7 @@ def run(self, dag):
                 if "u3" in self.basis:
                     new_op = U3Gate(*right_parameters)
                 else:
-                    raise TranspilerError("It was not possible to use the basis %s" % self.basis)
+                    raise TranspilerError(f"It was not possible to use the basis {self.basis}")
 
             dag.global_phase += right_global_phase
 
diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py
index 28ae67b321cb..acb23f39ab09 100644
--- a/qiskit/transpiler/passes/routing/sabre_swap.py
+++ b/qiskit/transpiler/passes/routing/sabre_swap.py
@@ -218,7 +218,7 @@ def run(self, dag):
         elif self.heuristic == "decay":
             heuristic = Heuristic.Decay
         else:
-            raise TranspilerError("Heuristic %s not recognized." % self.heuristic)
+            raise TranspilerError(f"Heuristic {self.heuristic} not recognized.")
         disjoint_utils.require_layout_isolated_to_component(
             dag, self.coupling_map if self.target is None else self.target
         )
diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py
index ec7ea8149137..3732802b770e 100644
--- a/qiskit/transpiler/passes/routing/stochastic_swap.py
+++ b/qiskit/transpiler/passes/routing/stochastic_swap.py
@@ -357,9 +357,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20):
 
                     # Give up if we fail again
                     if not success_flag:
-                        raise TranspilerError(
-                            "swap mapper failed: " + "layer %d, sublayer %d" % (i, j)
-                        )
+                        raise TranspilerError(f"swap mapper failed: layer {i}, sublayer {j}")
 
                     # Update the record of qubit positions
                     # for each inner iteration
diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
index 150874a84c7d..668d10f32bcc 100644
--- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py
@@ -540,8 +540,8 @@ def _synthesize_op_using_plugins(
             if isinstance(plugin_specifier, str):
                 if plugin_specifier not in hls_plugin_manager.method_names(op.name):
                     raise TranspilerError(
-                        "Specified method: %s not found in available plugins for %s"
-                        % (plugin_specifier, op.name)
+                        f"Specified method: {plugin_specifier} not found in available "
+                        f"plugins for {op.name}"
                     )
                 plugin_method = hls_plugin_manager.method(op.name, plugin_specifier)
             else:
diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py
index 61ddc71d131e..437718ec27b4 100644
--- a/qiskit/transpiler/passes/utils/check_map.py
+++ b/qiskit/transpiler/passes/utils/check_map.py
@@ -85,10 +85,8 @@ def _recurse(self, dag, wire_map) -> bool:
                 and not dag.has_calibration_for(node)
                 and (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) not in self.qargs
             ):
-                self.property_set["check_map_msg"] = "{}({}, {}) failed".format(
-                    node.name,
-                    wire_map[node.qargs[0]],
-                    wire_map[node.qargs[1]],
+                self.property_set["check_map_msg"] = (
+                    f"{node.name}({wire_map[node.qargs[0]]}, {wire_map[node.qargs[1]]}) failed"
                 )
                 return False
         return True
diff --git a/qiskit/transpiler/passes/utils/error.py b/qiskit/transpiler/passes/utils/error.py
index f2659ec052ff..44420582c1a3 100644
--- a/qiskit/transpiler/passes/utils/error.py
+++ b/qiskit/transpiler/passes/utils/error.py
@@ -43,7 +43,7 @@ def __init__(self, msg=None, action="raise"):
         if action in ["raise", "warn", "log"]:
             self.action = action
         else:
-            raise TranspilerError("Unknown action: %s" % action)
+            raise TranspilerError(f"Unknown action: {action}")
 
     def run(self, _):
         """Run the Error pass on `dag`."""
@@ -66,4 +66,4 @@ def run(self, _):
             logger = logging.getLogger(__name__)
             logger.info(msg)
         else:
-            raise TranspilerError("Unknown action: %s" % self.action)
+            raise TranspilerError(f"Unknown action: {self.action}")
diff --git a/qiskit/transpiler/passes/utils/fixed_point.py b/qiskit/transpiler/passes/utils/fixed_point.py
index fbef9d0a85ef..a85a7a8e6e7d 100644
--- a/qiskit/transpiler/passes/utils/fixed_point.py
+++ b/qiskit/transpiler/passes/utils/fixed_point.py
@@ -37,12 +37,12 @@ def __init__(self, property_to_check):
     def run(self, dag):
         """Run the FixedPoint pass on `dag`."""
         current_value = self.property_set[self._property]
-        fixed_point_previous_property = "_fixed_point_previous_%s" % self._property
+        fixed_point_previous_property = f"_fixed_point_previous_{self._property}"
 
         if self.property_set[fixed_point_previous_property] is None:
-            self.property_set["%s_fixed_point" % self._property] = False
+            self.property_set[f"{self._property}_fixed_point"] = False
         else:
             fixed_point_reached = self.property_set[fixed_point_previous_property] == current_value
-            self.property_set["%s_fixed_point" % self._property] = fixed_point_reached
+            self.property_set[f"{self._property}_fixed_point"] = fixed_point_reached
 
         self.property_set[fixed_point_previous_property] = deepcopy(current_value)
diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py
index 1b77e7dbd269..ec479f9e006b 100644
--- a/qiskit/transpiler/preset_passmanagers/common.py
+++ b/qiskit/transpiler/preset_passmanagers/common.py
@@ -518,7 +518,7 @@ def generate_translation_passmanager(
             ),
         ]
     else:
-        raise TranspilerError("Invalid translation method %s." % method)
+        raise TranspilerError(f"Invalid translation method {method}.")
     return PassManager(unroll)
 
 
@@ -557,7 +557,7 @@ def generate_scheduling(
         try:
             scheduling.append(scheduler[scheduling_method](instruction_durations, target=target))
         except KeyError as ex:
-            raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
+            raise TranspilerError(f"Invalid scheduling method {scheduling_method}.") from ex
     elif instruction_durations:
         # No scheduling. But do unit conversion for delays.
         def _contains_delay(property_set):
diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py
index 53daa4ccbe65..8805deece50e 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -411,7 +411,7 @@ def add_instruction(self, instruction, properties=None, name=None):
         if properties is None:
             properties = {None: None}
         if instruction_name in self._gate_map:
-            raise AttributeError("Instruction %s is already in the target" % instruction_name)
+            raise AttributeError(f"Instruction {instruction_name} is already in the target")
         self._gate_name_map[instruction_name] = instruction
         if is_class:
             qargs_val = {None: None}
@@ -1062,7 +1062,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False):
             for qargs, properties in self._gate_map[two_q_gate].items():
                 if len(qargs) != 2:
                     raise ValueError(
-                        "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate
+                        f"Specified two_q_gate: {two_q_gate} is not a 2 qubit instruction"
                     )
                 coupling_graph.add_edge(*qargs, {two_q_gate: properties})
             cmap = CouplingMap()
diff --git a/qiskit/user_config.py b/qiskit/user_config.py
index 73a68eb6bfd5..0ca52fc5c8c0 100644
--- a/qiskit/user_config.py
+++ b/qiskit/user_config.py
@@ -63,9 +63,9 @@ def read_config_file(self):
             if circuit_drawer:
                 if circuit_drawer not in ["text", "mpl", "latex", "latex_source", "auto"]:
                     raise exceptions.QiskitUserConfigError(
-                        "%s is not a valid circuit drawer backend. Must be "
+                        f"{circuit_drawer} is not a valid circuit drawer backend. Must be "
                         "either 'text', 'mpl', 'latex', 'latex_source', or "
-                        "'auto'." % circuit_drawer
+                        "'auto'."
                     )
                 self.settings["circuit_drawer"] = circuit_drawer
 
@@ -96,8 +96,8 @@ def read_config_file(self):
             if circuit_mpl_style:
                 if not isinstance(circuit_mpl_style, str):
                     warn(
-                        "%s is not a valid mpl circuit style. Must be "
-                        "a text string. Will not load style." % circuit_mpl_style,
+                        f"{circuit_mpl_style} is not a valid mpl circuit style. Must be "
+                        "a text string. Will not load style.",
                         UserWarning,
                         2,
                     )
@@ -112,8 +112,8 @@ def read_config_file(self):
                 for path in cpath_list:
                     if not os.path.exists(os.path.expanduser(path)):
                         warn(
-                            "%s is not a valid circuit mpl style path."
-                            " Correct the path in ~/.qiskit/settings.conf." % path,
+                            f"{path} is not a valid circuit mpl style path."
+                            " Correct the path in ~/.qiskit/settings.conf.",
                             UserWarning,
                             2,
                         )
diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py
index a1672dc1676c..146de9d32dee 100644
--- a/qiskit/visualization/circuit/circuit_visualization.py
+++ b/qiskit/visualization/circuit/circuit_visualization.py
@@ -345,8 +345,8 @@ def check_clbit_in_inst(circuit, cregbundle):
         )
     else:
         raise VisualizationError(
-            "Invalid output type %s selected. The only valid choices "
-            "are text, latex, latex_source, and mpl" % output
+            f"Invalid output type {output} selected. The only valid choices "
+            "are text, latex, latex_source, and mpl"
         )
     if image and interactive:
         image.show()
diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py
index 9341126bcd1a..4cb233277c0d 100644
--- a/qiskit/visualization/circuit/latex.py
+++ b/qiskit/visualization/circuit/latex.py
@@ -415,7 +415,7 @@ def _build_latex_array(self):
                         cwire_list = []
 
                     if len(wire_list) == 1 and not node.cargs:
-                        self._latex[wire_list[0]][column] = "\\gate{%s}" % gate_text
+                        self._latex[wire_list[0]][column] = f"\\gate{{{gate_text}}}"
 
                     elif isinstance(op, ControlledGate):
                         num_cols_op = self._build_ctrl_gate(op, gate_text, wire_list, column)
@@ -443,20 +443,20 @@ def _build_multi_gate(self, op, gate_text, wire_list, cwire_list, col):
             self._latex[wire_min][col] = (
                 f"\\multigate{{{wire_max - wire_min}}}{{{gate_text}}}_"
                 + "<" * (len(str(wire_ind)) + 2)
-                + "{%s}" % wire_ind
+                + f"{{{wire_ind}}}"
             )
             for wire in range(wire_min + 1, wire_max + 1):
                 if wire < cwire_start:
-                    ghost_box = "\\ghost{%s}" % gate_text
+                    ghost_box = f"\\ghost{{{gate_text}}}"
                     if wire in wire_list:
                         wire_ind = wire_list.index(wire)
                 else:
-                    ghost_box = "\\cghost{%s}" % gate_text
+                    ghost_box = f"\\cghost{{{gate_text}}}"
                     if wire in cwire_list:
                         wire_ind = cwire_list.index(wire)
                 if wire in wire_list + cwire_list:
                     self._latex[wire][col] = (
-                        ghost_box + "_" + "<" * (len(str(wire_ind)) + 2) + "{%s}" % wire_ind
+                        ghost_box + "_" + "<" * (len(str(wire_ind)) + 2) + f"{{{wire_ind}}}"
                     )
                 else:
                     self._latex[wire][col] = ghost_box
@@ -484,7 +484,7 @@ def _build_ctrl_gate(self, op, gate_text, wire_list, col):
             elif isinstance(op.base_gate, (U1Gate, PhaseGate)):
                 num_cols_op = self._build_symmetric_gate(op, gate_text, wire_list, col)
             else:
-                self._latex[wireqargs[0]][col] = "\\gate{%s}" % gate_text
+                self._latex[wireqargs[0]][col] = f"\\gate{{{gate_text}}}"
         else:
             # Treat special cases of swap and rzz gates
             if isinstance(op.base_gate, (SwapGate, RZZGate)):
@@ -527,7 +527,7 @@ def _build_symmetric_gate(self, op, gate_text, wire_list, col):
         )
         self._latex[wire_last][col] = "\\control \\qw"
         # Put side text to the right between bottom wire in wire_list and the one above it
-        self._latex[wire_max - 1][col + 1] = "\\dstick{\\hspace{2.0em}%s} \\qw" % gate_text
+        self._latex[wire_max - 1][col + 1] = f"\\dstick{{\\hspace{{2.0em}}{gate_text}}} \\qw"
         return 4  # num_cols for side text gates
 
     def _build_measure(self, node, col):
@@ -544,11 +544,9 @@ def _build_measure(self, node, col):
                 idx_str = str(self._circuit.find_bit(node.cargs[0]).registers[0][1])
             else:
                 wire2 = self._wire_map[node.cargs[0]]
-
-            self._latex[wire2][col] = "\\dstick{_{_{\\hspace{%sem}%s}}} \\cw \\ar @{<=} [-%s,0]" % (
-                cond_offset,
-                idx_str,
-                str(wire2 - wire1),
+            self._latex[wire2][col] = (
+                f"\\dstick{{_{{_{{\\hspace{{{cond_offset}em}}{idx_str}}}}}}} "
+                f"\\cw \\ar @{{<=}} [-{str(wire2 - wire1)},0]"
             )
         else:
             wire2 = self._wire_map[node.cargs[0]]
@@ -573,7 +571,7 @@ def _build_barrier(self, node, col):
             if node.op.label is not None:
                 pos = indexes[0]
                 label = node.op.label.replace(" ", "\\,")
-                self._latex[pos][col] = "\\cds{0}{^{\\mathrm{%s}}}" % label
+                self._latex[pos][col] = f"\\cds{{0}}{{^{{\\mathrm{{{label}}}}}}}"
 
     def _add_controls(self, wire_list, ctrlqargs, ctrl_state, col):
         """Add one or more controls to a gate"""
@@ -615,11 +613,10 @@ def _add_condition(self, op, wire_list, col):
             )
             gap = cwire - max(wire_list)
             control = "\\control" if op.condition[1] else "\\controlo"
-            self._latex[cwire][col] = f"{control}" + " \\cw^(%s){^{\\mathtt{%s}}} \\cwx[-%s]" % (
-                meas_offset,
-                label,
-                str(gap),
-            )
+            self._latex[cwire][
+                col
+            ] = f"{control} \\cw^({meas_offset}){{^{{\\mathtt{{{label}}}}}}} \\cwx[-{str(gap)}]"
+
         # If condition is a register and cregbundle is false
         else:
             # First sort the val_bits in the order of the register bits in the circuit
diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py
index 0076073fb8e4..9c4fa25309fc 100644
--- a/qiskit/visualization/circuit/matplotlib.py
+++ b/qiskit/visualization/circuit/matplotlib.py
@@ -371,7 +371,7 @@ def draw(self, filename=None, verbose=False):
         # Once the scaling factor has been determined, the global phase, register names
         # and numbers, wires, and gates are drawn
         if self._global_phase:
-            plt_mod.text(xl, yt, "Global Phase: %s" % pi_check(self._global_phase, output="mpl"))
+            plt_mod.text(xl, yt, f"Global Phase: {pi_check(self._global_phase, output='mpl')}")
         self._draw_regs_wires(num_folds, xmax, max_x_index, qubits_dict, clbits_dict, glob_data)
         self._draw_ops(
             self._nodes,
diff --git a/qiskit/visualization/circuit/text.py b/qiskit/visualization/circuit/text.py
index bec1ccf4a3ee..e9b7aa819e9b 100644
--- a/qiskit/visualization/circuit/text.py
+++ b/qiskit/visualization/circuit/text.py
@@ -759,7 +759,7 @@ def _repr_html_(self):
             "background: #fff0;"
             "line-height: 1.1;"
             'font-family: "Courier New",Courier,monospace">'
-            "%s
" % self.single_string() + f"{self.single_string()}" ) def __repr__(self): @@ -780,8 +780,9 @@ def single_string(self): ) except (UnicodeEncodeError, UnicodeDecodeError): warn( - "The encoding %s has a limited charset. Consider a different encoding in your " - "environment. UTF-8 is being used instead" % self.encoding, + f"The encoding {self.encoding} has a limited charset." + " Consider a different encoding in your " + "environment. UTF-8 is being used instead", RuntimeWarning, ) self.encoding = "utf-8" @@ -861,7 +862,7 @@ def lines(self, line_length=None): lines = [] if self.global_phase: - lines.append("global phase: %s" % pi_check(self.global_phase, ndigits=5)) + lines.append(f"global phase: {pi_check(self.global_phase, ndigits=5)}") for layer_group in layer_groups: wires = list(zip(*layer_group)) @@ -1168,7 +1169,7 @@ def add_connected_gate(node, gates, layer, current_cons, gate_wire_map): elif isinstance(op, RZZGate): # rzz - connection_label = "ZZ%s" % params + connection_label = f"ZZ{params}" gates = [Bullet(conditional=conditional), Bullet(conditional=conditional)] add_connected_gate(node, gates, layer, current_cons, gate_wire_map) @@ -1211,7 +1212,7 @@ def add_connected_gate(node, gates, layer, current_cons, gate_wire_map): add_connected_gate(node, gates, layer, current_cons, gate_wire_map) elif base_gate.name == "rzz": # crzz - connection_label = "ZZ%s" % params + connection_label = f"ZZ{params}" gates += [Bullet(conditional=conditional), Bullet(conditional=conditional)] elif len(rest) > 1: top_connect = "┴" if controlled_top else None diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index 73b9c30f6dc8..ad2fca6e9bcf 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -152,7 +152,7 @@ def node_attr_func(node): n["fillcolor"] = "lightblue" return n else: - raise VisualizationError("Unrecognized style %s for the dag_drawer." % style) + raise VisualizationError(f"Unrecognized style {style} for the dag_drawer.") edge_attr_func = None @@ -197,7 +197,7 @@ def node_attr_func(node): n["fillcolor"] = "red" return n else: - raise VisualizationError("Invalid style %s" % style) + raise VisualizationError(f"Invalid style {style}") def edge_attr_func(edge): e = {} diff --git a/qiskit/visualization/pulse_v2/core.py b/qiskit/visualization/pulse_v2/core.py index d60f2db030d0..20686f6fb4f6 100644 --- a/qiskit/visualization/pulse_v2/core.py +++ b/qiskit/visualization/pulse_v2/core.py @@ -220,7 +220,7 @@ def load_program( elif isinstance(program, (pulse.Waveform, pulse.SymbolicPulse)): self._waveform_loader(program) else: - raise VisualizationError("Data type %s is not supported." % type(program)) + raise VisualizationError(f"Data type {type(program)} is not supported.") # update time range self.set_time_range(0, program.duration, seconds=False) diff --git a/qiskit/visualization/pulse_v2/generators/frame.py b/qiskit/visualization/pulse_v2/generators/frame.py index 394f4b4aaf84..8b71b8596bb4 100644 --- a/qiskit/visualization/pulse_v2/generators/frame.py +++ b/qiskit/visualization/pulse_v2/generators/frame.py @@ -264,10 +264,9 @@ def gen_raw_operand_values_compact( freq_sci_notation = "0.0" else: abs_freq = np.abs(data.frame.freq) - freq_sci_notation = "{base:.1f}e{exp:d}".format( - base=data.frame.freq / (10 ** int(np.floor(np.log10(abs_freq)))), - exp=int(np.floor(np.log10(abs_freq))), - ) + base = data.frame.freq / (10 ** int(np.floor(np.log10(abs_freq)))) + exponent = int(np.floor(np.log10(abs_freq))) + freq_sci_notation = f"{base:.1f}e{exponent:d}" frame_info = f"{data.frame.phase:.2f}\n{freq_sci_notation}" text = drawings.TextData( diff --git a/qiskit/visualization/pulse_v2/generators/waveform.py b/qiskit/visualization/pulse_v2/generators/waveform.py index b0d90b895c7a..e770f271c454 100644 --- a/qiskit/visualization/pulse_v2/generators/waveform.py +++ b/qiskit/visualization/pulse_v2/generators/waveform.py @@ -203,11 +203,10 @@ def gen_ibmq_latex_waveform_name( if frac.numerator == 1: angle = rf"\pi/{frac.denominator:d}" else: - angle = r"{num:d}/{denom:d} \pi".format( - num=frac.numerator, denom=frac.denominator - ) + angle = rf"{frac.numerator:d}/{frac.denominator:d} \pi" else: # single qubit pulse + # pylint: disable-next=consider-using-f-string op_name = r"{{\rm {}}}".format(match_dict["op"]) angle_val = match_dict["angle"] if angle_val is None: @@ -217,9 +216,7 @@ def gen_ibmq_latex_waveform_name( if frac.numerator == 1: angle = rf"\pi/{frac.denominator:d}" else: - angle = r"{num:d}/{denom:d} \pi".format( - num=frac.numerator, denom=frac.denominator - ) + angle = rf"{frac.numerator:d}/{frac.denominator:d} \pi" latex_name = rf"{op_name}({sign}{angle})" else: latex_name = None @@ -490,7 +487,7 @@ def _draw_opaque_waveform( fill_objs.append(box_obj) # parameter name - func_repr = "{func}({params})".format(func=pulse_shape, params=", ".join(pnames)) + func_repr = f"{pulse_shape}({', '.join(pnames)})" text_style = { "zorder": formatter["layer.annotate"], @@ -630,8 +627,7 @@ def _parse_waveform( meta.update(acq_data) else: raise VisualizationError( - "Unsupported instruction {inst} by " - "filled envelope.".format(inst=inst.__class__.__name__) + f"Unsupported instruction {inst.__class__.__name__} by " "filled envelope." ) meta.update( diff --git a/qiskit/visualization/pulse_v2/layouts.py b/qiskit/visualization/pulse_v2/layouts.py index 6b39dceaf567..13b42e394e94 100644 --- a/qiskit/visualization/pulse_v2/layouts.py +++ b/qiskit/visualization/pulse_v2/layouts.py @@ -373,11 +373,7 @@ def detail_title(program: Union[pulse.Waveform, pulse.Schedule], device: DrawerB # add program duration dt = device.dt * 1e9 if device.dt else 1.0 - title_str.append( - "Duration: {dur:.1f} {unit}".format( - dur=program.duration * dt, unit="ns" if device.dt else "dt" - ) - ) + title_str.append(f"Duration: {program.duration * dt:.1f} {'ns' if device.dt else 'dt'}") # add device name if device.backend_name != "no-backend": diff --git a/qiskit/visualization/pulse_v2/plotters/matplotlib.py b/qiskit/visualization/pulse_v2/plotters/matplotlib.py index e92a34189994..1788a1254894 100644 --- a/qiskit/visualization/pulse_v2/plotters/matplotlib.py +++ b/qiskit/visualization/pulse_v2/plotters/matplotlib.py @@ -119,8 +119,7 @@ def draw(self): self.ax.add_patch(box) else: raise VisualizationError( - "Data {name} is not supported " - "by {plotter}".format(name=data, plotter=self.__class__.__name__) + f"Data {data} is not supported " f"by {self.__class__.__name__}" ) # axis break for pos in axis_config.axis_break_pos: diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py index e862b0208e28..0e47a5fe6d72 100644 --- a/qiskit/visualization/state_visualization.py +++ b/qiskit/visualization/state_visualization.py @@ -971,10 +971,10 @@ def plot_state_qsphere( if show_state_phases: element_angle = (np.angle(state[i]) + (np.pi * 4)) % (np.pi * 2) if use_degrees: - element_text += "\n$%.1f^\\circ$" % (element_angle * 180 / np.pi) + element_text += f"\n${element_angle * 180 / np.pi:.1f}^\\circ$" else: element_angle = pi_check(element_angle, ndigits=3).replace("pi", "\\pi") - element_text += "\n$%s$" % (element_angle) + element_text += f"\n${element_angle}$" ax.text( xvalue_text, yvalue_text, @@ -1463,11 +1463,10 @@ def state_drawer(state, output=None, **drawer_args): return draw_func(state, **drawer_args) except KeyError as err: raise ValueError( - """'{}' is not a valid option for drawing {} objects. Please choose from: + f"""'{output}' is not a valid option for drawing {type(state).__name__} + objects. Please choose from: 'text', 'latex', 'latex_source', 'qsphere', 'hinton', - 'bloch', 'city' or 'paulivec'.""".format( - output, type(state).__name__ - ) + 'bloch', 'city' or 'paulivec'.""" ) from err diff --git a/qiskit/visualization/timeline/plotters/matplotlib.py b/qiskit/visualization/timeline/plotters/matplotlib.py index 126d0981feda..daae6fe2558f 100644 --- a/qiskit/visualization/timeline/plotters/matplotlib.py +++ b/qiskit/visualization/timeline/plotters/matplotlib.py @@ -132,8 +132,7 @@ def draw(self): else: raise VisualizationError( - "Data {name} is not supported by {plotter}" - "".format(name=data, plotter=self.__class__.__name__) + f"Data {data} is not supported by {self.__class__.__name__}" ) def _time_bucket_outline( diff --git a/test/benchmarks/circuit_construction.py b/test/benchmarks/circuit_construction.py index 71c079476cd8..6c6c8733d25f 100644 --- a/test/benchmarks/circuit_construction.py +++ b/test/benchmarks/circuit_construction.py @@ -52,7 +52,7 @@ def time_circuit_copy(self, _, __): def build_parameterized_circuit(width, gates, param_count): - params = [Parameter("param-%s" % x) for x in range(param_count)] + params = [Parameter(f"param-{x}") for x in range(param_count)] param_iter = itertools.cycle(params) qr = QuantumRegister(width) diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index 121ad7222f4e..c1ece0230d39 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -167,7 +167,7 @@ def test_circuit_qasm_with_multiple_composite_circuits_with_same_name(self): my_gate_inst2_id = id(circuit.data[-1].operation) circuit.append(my_gate_inst3, [qr[0]]) my_gate_inst3_id = id(circuit.data[-1].operation) - + # pylint: disable-next=consider-using-f-string expected_qasm = """OPENQASM 2.0; include "qelib1.inc"; gate my_gate q0 {{ h q0; }} diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py index 3de2a451877b..7ef8751da0eb 100644 --- a/test/python/circuit/test_circuit_registers.py +++ b/test/python/circuit/test_circuit_registers.py @@ -97,7 +97,7 @@ def test_numpy_array_of_registers(self): """Test numpy array of Registers . See https://github.com/Qiskit/qiskit-terra/issues/1898 """ - qrs = [QuantumRegister(2, name="q%s" % i) for i in range(5)] + qrs = [QuantumRegister(2, name=f"q{i}") for i in range(5)] qreg_array = np.array([], dtype=object, ndmin=1) qreg_array = np.append(qreg_array, qrs) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index 4ac69278fd44..dbda9262f15b 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -423,17 +423,15 @@ def test_repr_of_instructions(self): ins1 = Instruction("test_instruction", 3, 5, [0, 1, 2, 3]) self.assertEqual( repr(ins1), - "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format( - ins1.name, ins1.num_qubits, ins1.num_clbits, ins1.params - ), + f"Instruction(name='{ins1.name}', num_qubits={ins1.num_qubits}, " + f"num_clbits={ins1.num_clbits}, params={ins1.params})", ) ins2 = random_circuit(num_qubits=4, depth=4, measure=True).to_instruction() self.assertEqual( repr(ins2), - "Instruction(name='{}', num_qubits={}, num_clbits={}, params={})".format( - ins2.name, ins2.num_qubits, ins2.num_clbits, ins2.params - ), + f"Instruction(name='{ins2.name}', num_qubits={ins2.num_qubits}, " + f"num_clbits={ins2.num_clbits}, params={ins2.params})", ) def test_instruction_condition_bits(self): diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index fd63057cff8d..feab3002f582 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -58,8 +58,8 @@ def raise_if_parameter_table_invalid(circuit): if circuit_parameters != table_parameters: raise CircuitError( "Circuit/ParameterTable Parameter mismatch. " - "Circuit parameters: {}. " - "Table parameters: {}.".format(circuit_parameters, table_parameters) + f"Circuit parameters: {circuit_parameters}. " + f"Table parameters: {table_parameters}." ) # Assert parameter locations in table are present in circuit. @@ -75,16 +75,15 @@ def raise_if_parameter_table_invalid(circuit): if not isinstance(instr.params[param_index], ParameterExpression): raise CircuitError( "ParameterTable instruction does not have a " - "ParameterExpression at param_index {}: {}." - "".format(param_index, instr) + f"ParameterExpression at param_index {param_index}: {instr}." ) if parameter not in instr.params[param_index].parameters: raise CircuitError( "ParameterTable instruction parameters does " "not match ParameterTable key. Instruction " - "parameters: {} ParameterTable key: {}." - "".format(instr.params[param_index].parameters, parameter) + f"parameters: {instr.params[param_index].parameters}" + f" ParameterTable key: {parameter}." ) # Assert circuit has no other parameter locations other than those in table. @@ -99,8 +98,8 @@ def raise_if_parameter_table_invalid(circuit): ): raise CircuitError( "Found parameterized instruction not " - "present in table. Instruction: {} " - "param_index: {}".format(instruction.operation, param_index) + f"present in table. Instruction: {instruction.operation} " + f"param_index: {param_index}" ) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 14033e522c62..0fcff29e5b40 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -83,8 +83,8 @@ def raise_if_dagcircuit_invalid(dag): ] if edges_outside_wires: raise DAGCircuitError( - "multi_graph contains one or more edges ({}) " - "not found in DAGCircuit.wires ({}).".format(edges_outside_wires, dag.wires) + f"multi_graph contains one or more edges ({edges_outside_wires}) " + f"not found in DAGCircuit.wires ({dag.wires})." ) # Every wire should have exactly one input node and one output node. @@ -134,9 +134,7 @@ def raise_if_dagcircuit_invalid(dag): all_bits = node_qubits | node_clbits | node_cond_bits assert in_wires == all_bits, f"In-edge wires {in_wires} != node bits {all_bits}" - assert out_wires == all_bits, "Out-edge wires {} != node bits {}".format( - out_wires, all_bits - ) + assert out_wires == all_bits, f"Out-edge wires {out_wires} != node bits {all_bits}" class TestDagRegisters(QiskitTestCase): diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 101e35acc8e1..d5c5507b3b8e 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -105,7 +105,7 @@ def setUpClass(cls): ) def test_circuit_on_fake_backend_v2(self, backend, optimization_level): if not optionals.HAS_AER and backend.num_qubits > 20: - self.skipTest("Unable to run fake_backend %s without qiskit-aer" % backend.name) + self.skipTest(f"Unable to run fake_backend {backend.name} without qiskit-aer") job = backend.run( transpile( self.circuit, backend, seed_transpiler=42, optimization_level=optimization_level @@ -126,8 +126,7 @@ def test_circuit_on_fake_backend_v2(self, backend, optimization_level): def test_circuit_on_fake_backend(self, backend, optimization_level): if not optionals.HAS_AER and backend.configuration().num_qubits > 20: self.skipTest( - "Unable to run fake_backend %s without qiskit-aer" - % backend.configuration().backend_name + f"Unable to run fake_backend {backend.configuration().backend_name} without qiskit-aer" ) job = backend.run( transpile( @@ -202,7 +201,7 @@ def test_defaults_to_dict(self, backend): self.assertGreater(i, 1e6) self.assertGreater(i, 1e6) else: - self.skipTest("Backend %s does not have defaults" % backend) + self.skipTest(f"Backend {backend} does not have defaults") def test_delay_circuit(self): backend = Fake27QPulseV1() diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index 3585efb9f646..043a9eca78b6 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -150,7 +150,7 @@ def test_append_1_qubit_gate(self): "sx", "sxdg", ): - with self.subTest(msg="append gate %s" % gate_name): + with self.subTest(msg=f"append gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff = _append_operation(cliff, gate_name, [0]) value_table = cliff.tableau[:, :-1] @@ -170,7 +170,7 @@ def test_1_qubit_identity_relations(self): """Tests identity relations for 1-qubit gates""" for gate_name in ("x", "y", "z", "h"): - with self.subTest(msg="identity for gate %s" % gate_name): + with self.subTest(msg=f"identity for gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() cliff = _append_operation(cliff, gate_name, [0]) @@ -181,7 +181,7 @@ def test_1_qubit_identity_relations(self): inv_gates = ["sdg", "sinv", "w"] for gate_name, inv_gate in zip(gates, inv_gates): - with self.subTest(msg="identity for gate %s" % gate_name): + with self.subTest(msg=f"identity for gate {gate_name}"): cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() cliff = _append_operation(cliff, gate_name, [0]) @@ -203,7 +203,7 @@ def test_1_qubit_mult_relations(self): ] for rel in rels: - with self.subTest(msg="relation %s" % rel): + with self.subTest(msg=f"relation {rel}"): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() @@ -227,7 +227,7 @@ def test_1_qubit_conj_relations(self): ] for rel in rels: - with self.subTest(msg="relation %s" % rel): + with self.subTest(msg=f"relation {rel}"): split_rel = rel.split() cliff = Clifford([[1, 0], [0, 1]]) cliff1 = cliff.copy() diff --git a/test/python/quantum_info/states/test_densitymatrix.py b/test/python/quantum_info/states/test_densitymatrix.py index 4f5c728604b5..cf6ad3c35090 100644 --- a/test/python/quantum_info/states/test_densitymatrix.py +++ b/test/python/quantum_info/states/test_densitymatrix.py @@ -398,7 +398,7 @@ def test_to_dict(self): target = {} for i in range(2): for j in range(3): - key = "{1}{0}|{1}{0}".format(i, j) + key = f"{j}{i}|{j}{i}" target[key] = 2 * j + i + 1 self.assertDictAlmostEqual(target, rho.to_dict()) @@ -407,7 +407,7 @@ def test_to_dict(self): target = {} for i in range(2): for j in range(11): - key = "{1},{0}|{1},{0}".format(i, j) + key = f"{j},{i}|{j},{i}" target[key] = 2 * j + i + 1 self.assertDictAlmostEqual(target, vec.to_dict()) diff --git a/test/python/result/test_mitigators.py b/test/python/result/test_mitigators.py index d290fc8ed486..66662bb587e1 100644 --- a/test/python/result/test_mitigators.py +++ b/test/python/result/test_mitigators.py @@ -140,22 +140,16 @@ def test_mitigation_improvement(self): self.assertLess( mitigated_error, unmitigated_error * 0.8, - "Mitigator {} did not improve circuit {} measurements".format( - mitigator, circuit_name - ), + f"Mitigator {mitigator} did not improve circuit {circuit_name} measurements", ) mitigated_stddev_upper_bound = mitigated_quasi_probs._stddev_upper_bound max_unmitigated_stddev = max(unmitigated_stddev.values()) self.assertGreaterEqual( mitigated_stddev_upper_bound, max_unmitigated_stddev, - "Mitigator {} on circuit {} gave stddev upper bound {} " - "while unmitigated stddev maximum is {}".format( - mitigator, - circuit_name, - mitigated_stddev_upper_bound, - max_unmitigated_stddev, - ), + f"Mitigator {mitigator} on circuit {circuit_name} gave stddev upper bound " + f"{mitigated_stddev_upper_bound} while unmitigated stddev maximum is " + f"{max_unmitigated_stddev}", ) def test_expectation_improvement(self): @@ -190,22 +184,15 @@ def test_expectation_improvement(self): self.assertLess( mitigated_error, unmitigated_error, - "Mitigator {} did not improve circuit {} expectation computation for diagonal {} " - "ideal: {}, unmitigated: {} mitigated: {}".format( - mitigator, - circuit_name, - diagonal, - ideal_expectation, - unmitigated_expectation, - mitigated_expectation, - ), + f"Mitigator {mitigator} did not improve circuit {circuit_name} expectation " + f"computation for diagonal {diagonal} ideal: {ideal_expectation}, unmitigated:" + f" {unmitigated_expectation} mitigated: {mitigated_expectation}", ) self.assertGreaterEqual( mitigated_stddev, unmitigated_stddev, - "Mitigator {} did not increase circuit {} the standard deviation".format( - mitigator, circuit_name - ), + f"Mitigator {mitigator} did not increase circuit {circuit_name} the" + f" standard deviation", ) def test_clbits_parameter(self): @@ -228,7 +215,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly marganalize for qubits 1,2".format(mitigator), + f"Mitigator {mitigator} did not correctly marganalize for qubits 1,2", ) mitigated_probs_02 = ( @@ -240,7 +227,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly marganalize for qubits 0,2".format(mitigator), + f"Mitigator {mitigator} did not correctly marganalize for qubits 0,2", ) def test_qubits_parameter(self): @@ -264,7 +251,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 0, 1, 2".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 0, 1, 2", ) mitigated_probs_210 = ( @@ -276,7 +263,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 2, 1, 0".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 2, 1, 0", ) mitigated_probs_102 = ( @@ -288,7 +275,7 @@ def test_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 1, 0, 2".format(mitigator), + "Mitigator {mitigator} did not correctly handle qubit order 1, 0, 2", ) def test_repeated_qubits_parameter(self): @@ -311,7 +298,7 @@ def test_repeated_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 2,1,0".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit order 2,1,0", ) # checking qubit order 2,1,0 should not "overwrite" the default 0,1,2 @@ -324,9 +311,8 @@ def test_repeated_qubits_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit order 0,1,2 (the expected default)".format( - mitigator - ), + f"Mitigator {mitigator} did not correctly handle qubit order 0,1,2 " + f"(the expected default)", ) def test_qubits_subset_parameter(self): @@ -350,7 +336,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit subset".format(mitigator), + "Mitigator {mitigator} did not correctly handle qubit subset", ) mitigated_probs_6 = ( @@ -362,7 +348,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.001, - "Mitigator {} did not correctly handle qubit subset".format(mitigator), + f"Mitigator {mitigator} did not correctly handle qubit subset", ) diagonal = str2diag("ZZ") ideal_expectation = 0 @@ -373,7 +359,7 @@ def test_qubits_subset_parameter(self): self.assertLess( mitigated_error, 0.1, - "Mitigator {} did not improve circuit expectation".format(mitigator), + f"Mitigator {mitigator} did not improve circuit expectation", ) def test_from_backend(self): diff --git a/test/python/synthesis/aqc/fast_gradient/test_layer1q.py b/test/python/synthesis/aqc/fast_gradient/test_layer1q.py index 43b164c42257..d2c5108391ff 100644 --- a/test/python/synthesis/aqc/fast_gradient/test_layer1q.py +++ b/test/python/synthesis/aqc/fast_gradient/test_layer1q.py @@ -62,7 +62,7 @@ def test_layer1q_matrix(self): # T == P^t @ G @ P. err = tut.relative_error(t_mat, iden[perm].T @ g_mat @ iden[perm]) - self.assertLess(err, eps, "err = {:0.16f}".format(err)) + self.assertLess(err, eps, f"err = {err:0.16f}") max_rel_err = max(max_rel_err, err) # Multiplication by permutation matrix of the left can be @@ -79,8 +79,7 @@ def test_layer1q_matrix(self): self.assertTrue( err1 < eps and err2 < eps and err3 < eps and err4 < eps, - "err1 = {:f}, err2 = {:f}, " - "err3 = {:f}, err4 = {:f}".format(err1, err2, err3, err4), + f"err1 = {err1:f}, err2 = {err2:f}, " f"err3 = {err3:f}, err4 = {err4:f}", ) max_rel_err = max(max_rel_err, err1, err2, err3, err4) @@ -128,12 +127,12 @@ def test_pmatrix_class(self): alt_ttmtt = pmat.finalize(temp_mat=tmp1) err1 = tut.relative_error(alt_ttmtt, ttmtt) - self.assertLess(err1, _eps, "relative error: {:f}".format(err1)) + self.assertLess(err1, _eps, f"relative error: {err1:f}") prod = np.complex128(np.trace(ttmtt @ t4)) alt_prod = pmat.product_q1(layer=c4, tmp1=tmp1, tmp2=tmp2) err2 = abs(alt_prod - prod) / abs(prod) - self.assertLess(err2, _eps, "relative error: {:f}".format(err2)) + self.assertLess(err2, _eps, f"relative error: {err2:f}") max_rel_err = max(max_rel_err, err1, err2) diff --git a/test/python/synthesis/aqc/fast_gradient/test_layer2q.py b/test/python/synthesis/aqc/fast_gradient/test_layer2q.py index 9de1e13df2d0..8f5655d6057e 100644 --- a/test/python/synthesis/aqc/fast_gradient/test_layer2q.py +++ b/test/python/synthesis/aqc/fast_gradient/test_layer2q.py @@ -65,7 +65,7 @@ def test_layer2q_matrix(self): # T == P^t @ G @ P. err = tut.relative_error(t_mat, iden[perm].T @ g_mat @ iden[perm]) - self.assertLess(err, _eps, "err = {:0.16f}".format(err)) + self.assertLess(err, _eps, f"err = {err:0.16f}") max_rel_err = max(max_rel_err, err) # Multiplication by permutation matrix of the left can be @@ -82,8 +82,8 @@ def test_layer2q_matrix(self): self.assertTrue( err1 < _eps and err2 < _eps and err3 < _eps and err4 < _eps, - "err1 = {:f}, err2 = {:f}, " - "err3 = {:f}, err4 = {:f}".format(err1, err2, err3, err4), + f"err1 = {err1:f}, err2 = {err2:f}, " + f"err3 = {err3:f}, err4 = {err4:f}", ) max_rel_err = max(max_rel_err, err1, err2, err3, err4) @@ -136,12 +136,12 @@ def test_pmatrix_class(self): alt_ttmtt = pmat.finalize(temp_mat=tmp1) err1 = tut.relative_error(alt_ttmtt, ttmtt) - self.assertLess(err1, _eps, "relative error: {:f}".format(err1)) + self.assertLess(err1, _eps, f"relative error: {err1:f}") prod = np.complex128(np.trace(ttmtt @ t4)) alt_prod = pmat.product_q2(layer=c4, tmp1=tmp1, tmp2=tmp2) err2 = abs(alt_prod - prod) / abs(prod) - self.assertLess(err2, _eps, "relative error: {:f}".format(err2)) + self.assertLess(err2, _eps, f"relative error: {err2:f}") max_rel_err = max(max_rel_err, err1, err2) diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index a879d5251f90..050df5a3fe1c 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -78,17 +78,13 @@ def test_invalid_permutations(self, width): pattern_out_of_range[0] = width with self.assertRaises(ValueError) as exc: _validate_permutation(pattern_out_of_range) - self.assertIn( - "input has length {0} and contains {0}".format(width), str(exc.exception) - ) + self.assertIn(f"input has length {width} and contains {width}", str(exc.exception)) pattern_duplicate = np.copy(pattern) pattern_duplicate[-1] = pattern[0] with self.assertRaises(ValueError) as exc: _validate_permutation(pattern_duplicate) - self.assertIn( - "input contains {} more than once".format(pattern[0]), str(exc.exception) - ) + self.assertIn(f"input contains {pattern[0]} more than once", str(exc.exception)) @data(4, 5, 10, 15, 20) def test_synth_permutation_basic(self, width): diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py index ecc4ffaaa966..5b63462963c2 100644 --- a/test/python/test_user_config.py +++ b/test/python/test_user_config.py @@ -25,7 +25,7 @@ class TestUserConfig(QiskitTestCase): def setUp(self): super().setUp() - self.file_path = "test_%s.conf" % uuid4() + self.file_path = f"test_{uuid4()}.conf" def test_empty_file_read(self): config = user_config.UserConfig(self.file_path) diff --git a/test/python/transpiler/test_pass_scheduler.py b/test/python/transpiler/test_pass_scheduler.py index 6d6026d6148e..12ba81c78a6c 100644 --- a/test/python/transpiler/test_pass_scheduler.py +++ b/test/python/transpiler/test_pass_scheduler.py @@ -703,7 +703,7 @@ def assertPassLog(self, passmanager, list_of_passes): output_lines = self.output.readlines() pass_log_lines = [x for x in output_lines if x.startswith("Pass:")] for index, pass_name in enumerate(list_of_passes): - self.assertTrue(pass_log_lines[index].startswith("Pass: %s -" % pass_name)) + self.assertTrue(pass_log_lines[index].startswith(f"Pass: {pass_name} -")) def test_passes(self): """Dump passes in different FlowControllerLinear""" diff --git a/test/python/visualization/timeline/test_generators.py b/test/python/visualization/timeline/test_generators.py index 66afe3556b3d..5554248089ed 100644 --- a/test/python/visualization/timeline/test_generators.py +++ b/test/python/visualization/timeline/test_generators.py @@ -109,9 +109,7 @@ def test_gen_full_gate_name_with_finite_duration(self): self.assertListEqual(list(drawing_obj.yvals), [0.0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u3(0.00, 0.00, 0.00)[20]") - ref_latex = "{name}(0.00, 0.00, 0.00)[20]".format( - name=self.formatter["latex_symbol.gates"]["u3"] - ) + ref_latex = f"{self.formatter['latex_symbol.gates']['u3']}(0.00, 0.00, 0.00)[20]" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -132,7 +130,7 @@ def test_gen_full_gate_name_with_zero_duration(self): self.assertListEqual(list(drawing_obj.yvals), [self.formatter["label_offset.frame_change"]]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u1(0.00)") - ref_latex = "{name}(0.00)".format(name=self.formatter["latex_symbol.gates"]["u1"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u1']}(0.00)" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -159,7 +157,7 @@ def test_gen_short_gate_name_with_finite_duration(self): self.assertListEqual(list(drawing_obj.yvals), [0.0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u3") - ref_latex = "{name}".format(name=self.formatter["latex_symbol.gates"]["u3"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u3']}" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -180,7 +178,7 @@ def test_gen_short_gate_name_with_zero_duration(self): self.assertListEqual(list(drawing_obj.yvals), [self.formatter["label_offset.frame_change"]]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "u1") - ref_latex = "{name}".format(name=self.formatter["latex_symbol.gates"]["u1"]) + ref_latex = f"{self.formatter['latex_symbol.gates']['u1']}" self.assertEqual(drawing_obj.latex, ref_latex) ref_styles = { @@ -250,6 +248,7 @@ def test_gen_bit_name(self): self.assertListEqual(list(drawing_obj.yvals), [0]) self.assertListEqual(drawing_obj.bits, [self.qubit]) self.assertEqual(drawing_obj.text, "bar") + # pylint: disable-next=consider-using-f-string ref_latex = r"{{\rm {register}}}_{{{index}}}".format(register="q", index="0") self.assertEqual(drawing_obj.latex, ref_latex) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 2dde71a5d39d..04dced90dfaa 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -306,10 +306,9 @@ def equivalent_transpile(self, kwargs): count_differences = dicts_almost_equal(aer_counts, xpiled_aer_counts, 0.05 * shots) - assert ( - count_differences == "" - ), "Counts not equivalent: {}\nFailing QASM Input:\n{}\n\nFailing QASM Output:\n{}".format( - count_differences, qasm2.dumps(self.qc), qasm2.dumps(xpiled_qc) + assert count_differences == "", ( + f"Counts not equivalent: {count_differences}\nFailing QASM Input:\n" + f"{qasm2.dumps(self.qc)}\n\nFailing QASM Output:\n{qasm2.dumps(xpiled_qc)}" ) diff --git a/test/utils/base.py b/test/utils/base.py index 747be7f66b51..63a8bf4384f0 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -81,10 +81,10 @@ def setUp(self): self.addTypeEqualityFunc(QuantumCircuit, self.assertQuantumCircuitEqual) if self.__setup_called: raise ValueError( - "In File: %s\n" + f"In File: {(sys.modules[self.__class__.__module__].__file__,)}\n" "TestCase.setUp was already called. Do not explicitly call " "setUp from your tests. In your own setUp, use super to call " - "the base setUp." % (sys.modules[self.__class__.__module__].__file__,) + "the base setUp." ) self.__setup_called = True @@ -92,10 +92,10 @@ def tearDown(self): super().tearDown() if self.__teardown_called: raise ValueError( - "In File: %s\n" + f"In File: {(sys.modules[self.__class__.__module__].__file__,)}\n" "TestCase.tearDown was already called. Do not explicitly call " "tearDown from your tests. In your own tearDown, use super to " - "call the base tearDown." % (sys.modules[self.__class__.__module__].__file__,) + "call the base tearDown." ) self.__teardown_called = True @@ -305,10 +305,10 @@ def valid_comparison(value): if places is not None: if delta is not None: raise TypeError("specify delta or places not both") - msg_suffix = " within %s places" % places + msg_suffix = f" within {places} places" else: delta = delta or 1e-8 - msg_suffix = " within %s delta" % delta + msg_suffix = f" within {delta} delta" # Compare all keys in both dicts, populating error_msg. error_msg = "" diff --git a/test/visual/results.py b/test/visual/results.py index efaf09bbbbfa..76fde794b394 100644 --- a/test/visual/results.py +++ b/test/visual/results.py @@ -83,30 +83,30 @@ def _new_gray(size, color): @staticmethod def passed_result_html(result, reference, diff, title): """Creates the html for passing tests""" - ret = '
%s ' % title + ret = f'
{title} ' ret += "" - ret += '
' % result - ret += '' % reference - ret += '' % diff + ret += f'
' + ret += f'' + ret += f'' ret += "
" return ret @staticmethod def failed_result_html(result, reference, diff, title): """Creates the html for failing tests""" - ret = '
%s ' % title + ret = f'
{title} ' ret += "" - ret += '
' % result - ret += '' % reference - ret += '' % diff + ret += f'
' + ret += f'' + ret += f'' ret += "
" return ret @staticmethod def no_reference_html(result, title): """Creates the html for missing-reference tests""" - ret = '
%s ' % title - ret += '" % (name, fullpath_name, fullpath_reference) + f'Download this image' + f" to {fullpath_reference}" + " and add/push to the repo" ) ret += Results.no_reference_html(fullpath_name, title) ret += "" diff --git a/tools/build_standard_commutations.py b/tools/build_standard_commutations.py index 56c452b11ce0..0e1fcdf1797d 100644 --- a/tools/build_standard_commutations.py +++ b/tools/build_standard_commutations.py @@ -143,12 +143,14 @@ def _dump_commuting_dict_as_python( dir_str = "standard_gates_commutations = {\n" for k, v in commutations.items(): if not isinstance(v, dict): + # pylint: disable-next=consider-using-f-string dir_str += ' ("{}", "{}"): {},\n'.format(*k, v) else: + # pylint: disable-next=consider-using-f-string dir_str += ' ("{}", "{}"): {{\n'.format(*k) for entry_key, entry_val in v.items(): - dir_str += " {}: {},\n".format(entry_key, entry_val) + dir_str += f" {entry_key}: {entry_val},\n" dir_str += " },\n" dir_str += "}\n" diff --git a/tools/find_stray_release_notes.py b/tools/find_stray_release_notes.py index 7e04f5ecc320..d694e0d89b8f 100755 --- a/tools/find_stray_release_notes.py +++ b/tools/find_stray_release_notes.py @@ -49,7 +49,7 @@ def _main(): failed_files = [x for x in res if x is not None] if len(failed_files) > 0: for failed_file in failed_files: - sys.stderr.write("%s is not in the correct location.\n" % failed_file) + sys.stderr.write(f"{failed_file} is not in the correct location.\n") sys.exit(1) sys.exit(0) diff --git a/tools/verify_headers.py b/tools/verify_headers.py index 7bd7d2bad4e7..552372b7725a 100755 --- a/tools/verify_headers.py +++ b/tools/verify_headers.py @@ -88,18 +88,18 @@ def validate_header(file_path): break if file_path.endswith(".rs"): if "".join(lines[start : start + 2]) != header_rs: - return (file_path, False, "Header up to copyright line does not match: %s" % header) + return (file_path, False, f"Header up to copyright line does not match: {header}") if not copyright_line.search(lines[start + 2]): return (file_path, False, "Header copyright line not found") if "".join(lines[start + 3 : start + 11]) != apache_text_rs: - return (file_path, False, "Header apache text string doesn't match:\n %s" % apache_text) + return (file_path, False, f"Header apache text string doesn't match:\n {apache_text}") else: if "".join(lines[start : start + 2]) != header: - return (file_path, False, "Header up to copyright line does not match: %s" % header) + return (file_path, False, f"Header up to copyright line does not match: {header}") if not copyright_line.search(lines[start + 2]): return (file_path, False, "Header copyright line not found") if "".join(lines[start + 3 : start + 11]) != apache_text: - return (file_path, False, "Header apache text string doesn't match:\n %s" % apache_text) + return (file_path, False, f"Header apache text string doesn't match:\n {apache_text}") return (file_path, True, None) @@ -122,8 +122,8 @@ def _main(): failed_files = [x for x in res if x[1] is False] if len(failed_files) > 0: for failed_file in failed_files: - sys.stderr.write("%s failed header check because:\n" % failed_file[0]) - sys.stderr.write("%s\n\n" % failed_file[2]) + sys.stderr.write(f"{failed_file[0]} failed header check because:\n") + sys.stderr.write(f"{failed_file[2]}\n\n") sys.exit(1) sys.exit(0) From 0f513577b31c984520c8598a84b39149513ed115 Mon Sep 17 00:00:00 2001 From: Luis J Camargo Date: Wed, 19 Jun 2024 09:50:03 -0600 Subject: [PATCH 156/179] Spellcheck Done [Unitary Hack 2024] (#12501) * spell check iter1 * spell check iter 2 * Fix fmt * Update qiskit/_numpy_compat.py * Update qiskit/synthesis/evolution/product_formula.py * Update qiskit/synthesis/evolution/product_formula.py * Update releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml * undo some corrections --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Co-authored-by: Luciano Bello Co-authored-by: Julien Gacon --- .binder/postBuild | 2 +- .github/workflows/backport.yml | 2 +- CONTRIBUTING.md | 4 +-- crates/README.md | 8 +++--- crates/accelerate/src/pauli_exp_val.rs | 8 +++--- crates/accelerate/src/sabre/sabre_dag.rs | 2 +- crates/accelerate/src/sparse_pauli_op.rs | 8 +++--- crates/accelerate/src/two_qubit_decompose.rs | 4 +-- crates/qasm2/src/expr.rs | 6 ++-- crates/qasm2/src/lex.rs | 6 ++-- crates/qasm2/src/parse.rs | 2 +- crates/qasm3/src/build.rs | 4 +-- crates/qasm3/src/circuit.rs | 2 +- crates/qasm3/src/expr.rs | 2 +- docs/conf.py | 4 +-- qiskit/_numpy_compat.py | 2 +- qiskit/circuit/__init__.py | 2 +- qiskit/circuit/_classical_resource_map.py | 10 +++---- qiskit/circuit/classical/expr/expr.py | 8 +++--- qiskit/circuit/classical/expr/visitors.py | 2 +- qiskit/circuit/classical/types/__init__.py | 2 +- qiskit/circuit/classical/types/types.py | 4 +-- qiskit/circuit/controlflow/_builder_utils.py | 2 +- qiskit/circuit/controlflow/builder.py | 8 +++--- qiskit/circuit/controlflow/if_else.py | 4 +-- qiskit/circuit/controlflow/switch_case.py | 2 +- qiskit/circuit/instruction.py | 6 ++-- .../arithmetic/linear_amplitude_function.py | 2 +- qiskit/circuit/library/n_local/n_local.py | 4 +-- qiskit/circuit/library/standard_gates/u.py | 2 +- qiskit/circuit/library/standard_gates/x.py | 2 +- qiskit/circuit/parameter.py | 2 +- qiskit/circuit/parameterexpression.py | 6 ++-- qiskit/circuit/quantumcircuit.py | 28 +++++++++---------- qiskit/circuit/random/utils.py | 7 +++-- qiskit/circuit/singleton.py | 6 ++-- qiskit/converters/circuit_to_instruction.py | 2 +- qiskit/dagcircuit/dagcircuit.py | 4 +-- qiskit/passmanager/passmanager.py | 4 +-- qiskit/primitives/base/base_estimator.py | 2 +- .../primitives/containers/bindings_array.py | 4 +-- qiskit/providers/backend.py | 2 +- qiskit/providers/options.py | 2 +- qiskit/qasm2/__init__.py | 14 +++++----- qiskit/qasm2/export.py | 2 +- qiskit/qasm2/parse.py | 2 +- qiskit/qasm3/ast.py | 2 +- qiskit/qasm3/exporter.py | 6 ++-- qiskit/qobj/converters/pulse_instruction.py | 4 +-- qiskit/qpy/binary_io/schedules.py | 2 +- qiskit/qpy/type_keys.py | 4 +-- .../operators/channel/transformations.py | 2 +- .../operators/dihedral/dihedral.py | 4 +-- qiskit/quantum_info/operators/measures.py | 2 +- .../operators/symplectic/clifford.py | 2 +- .../operators/symplectic/pauli_list.py | 2 +- .../operators/symplectic/random.py | 2 +- qiskit/quantum_info/states/stabilizerstate.py | 10 +++---- qiskit/result/counts.py | 2 +- .../clifford/clifford_decompose_layers.py | 2 +- .../cnotdihedral_decompose_full.py | 2 +- .../cnotdihedral_decompose_general.py | 2 +- .../discrete_basis/solovay_kitaev.py | 2 +- qiskit/synthesis/linear/linear_depth_lnn.py | 2 +- .../synthesis/linear_phase/cx_cz_depth_lnn.py | 6 ++-- qiskit/synthesis/linear_phase/cz_depth_lnn.py | 2 +- .../stabilizer/stabilizer_decompose.py | 2 +- .../two_qubit/two_qubit_decompose.py | 2 +- .../synthesis/unitary/aqc/cnot_structures.py | 2 +- qiskit/synthesis/unitary/qsd.py | 2 +- .../passes/basis/basis_translator.py | 4 +-- .../transpiler/passes/layout/sabre_layout.py | 4 +-- qiskit/transpiler/passes/layout/vf2_layout.py | 2 +- .../optimization/commutative_cancellation.py | 2 +- .../template_matching/backward_match.py | 2 +- .../template_substitution.py | 2 +- .../passes/routing/star_prerouting.py | 2 +- .../passes/scheduling/alignments/__init__.py | 4 +-- .../passes/scheduling/base_scheduler.py | 2 +- .../passes/scheduling/dynamical_decoupling.py | 2 +- .../passes/scheduling/padding/base_padding.py | 2 +- .../padding/dynamical_decoupling.py | 4 +-- .../passes/synthesis/unitary_synthesis.py | 2 +- .../transpiler/passes/utils/gate_direction.py | 2 +- qiskit/transpiler/passmanager.py | 2 +- qiskit/utils/classtools.py | 6 ++-- qiskit/utils/lazy_tester.py | 4 +-- qiskit/utils/optionals.py | 16 +++++------ qiskit/visualization/circuit/_utils.py | 2 +- qiskit/visualization/pulse_v2/events.py | 2 +- releasenotes/config.yaml | 4 +-- .../0.12/operator-dot-fd90e7e5ad99ff9b.yaml | 2 +- .../0.13/0.13.0-release-a92553cf72c203aa.yaml | 2 +- ...e-job-status-methods-3ab9646c5f5470a6.yaml | 2 +- ...efault-schedule-name-51ba198cf08978cd.yaml | 2 +- .../qinfo-operators-0193871295190bad.yaml | 8 +++--- .../0.13/qinfo-states-7f67e2432cf0c12c.yaml | 2 +- ...sition-visualization-a62d0d119569fa05.yaml | 2 +- .../parameter-conjugate-a16fd7ae0dc18ede.yaml | 2 +- .../delay-in-circuit-33f0d81783ac12ea.yaml | 2 +- ...n-setting-ctrl_state-2f9af3b9f0f7903f.yaml | 2 +- .../remove-dagnode-dict-32fa35479c0a8331.yaml | 2 +- .../add-schedule-block-c37527f3205b7b62.yaml | 2 +- ...asicaer-new-provider-ea7cf756df231c2b.yaml | 2 +- .../deprecate-schemas-424c29fbd35c90de.yaml | 2 +- .../notes/0.17/ecr-gate-45cfda1b84ac792c.yaml | 2 +- ...ircular-entanglement-0acf0195138b6aa2.yaml | 2 +- ...e-time-visualization-b5404ad875cbdae4.yaml | 2 +- .../0.17/issue-5751-1b6249f6263c9c30.yaml | 2 +- ...skit-version-wrapper-90cb7fcffeaafd6a.yaml | 4 +-- ...replace-pulse-drawer-f9f667c8f71e1e02.yaml | 2 +- .../0.18/add-pauli-list-5644d695f91de808.yaml | 2 +- ...nite-job-submissions-d6f6a583535ca798.yaml | 2 +- .../gates-in-basis-pass-337f6637e61919db.yaml | 2 +- ...measure_all-add_bits-8525317935197b90.yaml | 2 +- .../notes/0.19/mpl-bump-33a1240266e66508.yaml | 2 +- ...t-mitigation-classes-2ef175e232d791ae.yaml | 4 +-- ...nual-warning-filters-028646b73bb86860.yaml | 8 +++--- ...parse-pauli-internal-8226b4f57a61b982.yaml | 2 +- .../0.19/vf2layout-4cea88087c355769.yaml | 2 +- ...erances-z2symmetries-9c444a7b1237252e.yaml | 2 +- ...ion-alignment-passes-ef0f20d4f89f95f3.yaml | 8 +++--- ...t-preset-passmanager-db46513a24e79aa9.yaml | 2 +- .../marginal-memory-29d9d6586ae78590.yaml | 2 +- .../vf2-post-layout-f0213e2c7ebb645c.yaml | 2 +- ...plementation-details-09b0ead8b42cacda.yaml | 2 +- ...-entanglement-nlocal-38581e4ffb7a7c68.yaml | 2 +- ...-flow-representation-09520e2838f0657e.yaml | 2 +- ...ate-direction-target-a9f0acd0cf30ed66.yaml | 2 +- .../0.22/primitive-run-5d1afab3655330a6.yaml | 2 +- ...lic-pulse-subclasses-77314a1654521852.yaml | 4 +-- ...steppable-optimizers-9d9b48ba78bd58bb.yaml | 2 +- ...nsored-subset-fitter-bd28e6e6ec5bdaae.yaml | 2 +- ...ation-reorganisation-9e302239705c7842.yaml | 2 +- .../fix-qpy-loose-bits-5283dc4ad3823ce3.yaml | 4 +-- .../notes/0.23/fix_8897-2a90c4b0857c19c2.yaml | 2 +- .../0.23/initial_state-8e20b04fc2ec2f4b.yaml | 2 +- ...ize-1q-decomposition-cb9bb4651607b639.yaml | 2 +- ...l-custom-definitions-a1b839de199ca048.yaml | 4 +-- .../add-hls-plugins-038388970ad43c55.yaml | 2 +- ...-new-symbolic-pulses-4dc46ecaaa1ba928.yaml | 2 +- ...eprecate-bip-mapping-f0025c4c724e1ec8.yaml | 2 +- ...rcuit-data-operation-1b8326b1b089f10c.yaml | 2 +- ...tensoredop-to-matrix-6f22644f1bdb8b41.yaml | 2 +- ...es-for-pulse-scaling-8369eb584c6d8fe1.yaml | 2 +- ...sm2-exporter-rewrite-8993dd24f930b180.yaml | 2 +- .../qasm2-parser-rust-ecf6570e2d445a94.yaml | 2 +- ...ints-list-optimizers-033d7439f86bbb71.yaml | 2 +- ...-propagate-condition-898052b53edb1f17.yaml | 2 +- ...ter-parameter-rebind-3c799e74456469d9.yaml | 2 +- ...-mcrz-relative-phase-6ea81a369f8bda38.yaml | 2 +- .../notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml | 2 +- ...latten-nlocal-family-292b23b99947f3c9.yaml | 2 +- .../normalize-stateprep-e21972dce8695509.yaml | 2 +- .../0.25/qpy-layout-927ab34f2b47f4aa.yaml | 2 +- ...en-swapper-rustworkx-9e02c0ab67a59fe8.yaml | 2 +- .../dag-appenders-check-84d4ef20c1e20fd0.yaml | 2 +- ...deprecate-duplicates-a871f83bbbe1c96f.yaml | 2 +- ...-basis-gatedirection-bdffad3b47c1c532.yaml | 2 +- ...pr-rvalue-conditions-8b5d5f7c015658c0.yaml | 2 +- .../fix-parameter-hash-d22c270090ffc80e.yaml | 4 +-- ...-unscheduled-warning-873f7a24c6b51e2c.yaml | 6 ++-- .../0.45/qasm2-new-api-4e1e4803d6a5a175.yaml | 2 +- .../0.45/singletons-83782de8bd062cbc.yaml | 2 +- .../changes-on-upgrade-6fcd573269a8ebc5.yaml | 2 +- .../new-features-0.9-159645f977a139f7.yaml | 4 +-- ...move-opflow-qi-utils-3debd943c65b17da.yaml | 2 +- ...fix-qdrift-evolution-bceb9c4f182ab0f5.yaml | 2 +- .../1.1/star-prerouting-0998b59880c20cef.yaml | 2 +- ...r_probabilities_dict-e53f524d115bbcfc.yaml | 2 +- setup.py | 2 +- test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm | 2 +- .../classical/test_expr_constructors.py | 4 +-- .../circuit/classical/test_expr_properties.py | 2 +- test/python/circuit/library/test_diagonal.py | 2 +- test/python/circuit/library/test_qft.py | 2 +- .../circuit/test_circuit_load_from_qpy.py | 4 +-- .../python/circuit/test_circuit_operations.py | 2 +- test/python/circuit/test_circuit_vars.py | 2 +- .../circuit/test_control_flow_builders.py | 8 +++--- test/python/circuit/test_controlled_gate.py | 2 +- test/python/circuit/test_instructions.py | 2 +- test/python/circuit/test_parameters.py | 6 ++-- test/python/compiler/test_assembler.py | 6 ++-- test/python/compiler/test_transpiler.py | 6 ++-- .../converters/test_circuit_to_instruction.py | 4 +-- test/python/dagcircuit/test_collect_blocks.py | 2 +- test/python/dagcircuit/test_dagcircuit.py | 10 +++---- .../containers/test_observables_array.py | 2 +- test/python/primitives/test_estimator.py | 8 +++--- .../primitives/test_statevector_estimator.py | 2 +- .../pulse/test_instruction_schedule_map.py | 4 +-- test/python/pulse/test_reference.py | 6 ++-- test/python/qasm3/test_export.py | 8 +++--- test/python/qasm3/test_import.py | 2 +- .../operators/symplectic/test_pauli_list.py | 4 +-- .../symplectic/test_sparse_pauli_op.py | 6 ++-- test/python/result/test_mitigators.py | 4 +-- test/python/result/test_result.py | 2 +- .../test_clifford_decompose_layers.py | 4 +-- test/python/synthesis/test_cx_cz_synthesis.py | 2 +- test/python/synthesis/test_cz_synthesis.py | 2 +- .../synthesis/test_stabilizer_synthesis.py | 4 +-- test/python/synthesis/test_synthesis.py | 2 +- test/python/test_util.py | 4 +-- .../test_instruction_alignments.py | 2 +- .../python/transpiler/test_clifford_passes.py | 2 +- .../test_commutative_cancellation.py | 10 +++---- .../transpiler/test_consolidate_blocks.py | 2 +- .../test_full_ancilla_allocation.py | 2 +- test/python/transpiler/test_gate_direction.py | 4 +-- .../transpiler/test_high_level_synthesis.py | 4 +-- .../transpiler/test_instruction_alignments.py | 2 +- .../transpiler/test_preset_passmanagers.py | 4 +-- test/python/transpiler/test_sabre_layout.py | 2 +- test/python/transpiler/test_sabre_swap.py | 2 +- .../transpiler/test_template_matching.py | 4 +-- test/python/transpiler/test_token_swapper.py | 2 +- .../test_unitary_synthesis_plugin.py | 4 +-- test/python/utils/test_lazy_loaders.py | 2 +- .../visualization/test_circuit_text_drawer.py | 4 +-- test/qpy_compat/test_qpy.py | 2 +- test/utils/_canonical.py | 2 +- 223 files changed, 370 insertions(+), 369 deletions(-) diff --git a/.binder/postBuild b/.binder/postBuild index 9517953258f2..cb06527f280e 100644 --- a/.binder/postBuild +++ b/.binder/postBuild @@ -7,7 +7,7 @@ # - pylatexenc: for MPL drawer # - pillow: for image comparison # - appmode: jupyter extension for executing the notebook -# - seaborn: visualisation pacakge required for some graphs +# - seaborn: visualization pacakge required for some graphs pip install matplotlib pylatexenc pillow appmode seaborn pip install . diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index bcc86d63fcf5..88fd919e8ad6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,6 +1,6 @@ name: Backport metadata -# Mergify manages the opening of the backport PR, this workflow is just to extend its behaviour to +# Mergify manages the opening of the backport PR, this workflow is just to extend its behavior to # do useful things like copying across the tagged labels and milestone from the base PR. on: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4641c7878fc1..7076c1571b18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -532,7 +532,7 @@ we used in our CI systems more closely. ### Snapshot Testing for Visualizations -If you are working on code that makes changes to any matplotlib visualisations +If you are working on code that makes changes to any matplotlib visualizations you will need to check that your changes don't break any snapshot tests, and add new tests where necessary. You can do this as follows: @@ -543,7 +543,7 @@ the snapshot tests (note this may take some time to finish loading). 3. Each test result provides a set of 3 images (left: reference image, middle: your test result, right: differences). In the list of tests the passed tests are collapsed and failed tests are expanded. If a test fails, you will see a situation like this: Screenshot_2021-03-26_at_14 13 54 -4. Fix any broken tests. Working on code for one aspect of the visualisations +4. Fix any broken tests. Working on code for one aspect of the visualizations can sometimes result in minor changes elsewhere to spacing etc. In these cases you just need to update the reference images as follows: - download the mismatched images (link at top of Jupyter Notebook output) diff --git a/crates/README.md b/crates/README.md index cbe58afa07d1..d72247bc61de 100644 --- a/crates/README.md +++ b/crates/README.md @@ -29,11 +29,11 @@ This would be a particular problem for defining the circuit object and using it ## Developer notes -### Beware of initialisation order +### Beware of initialization order -The Qiskit C extension `qiskit._accelerate` needs to be initialised in a single go. -It is the lowest part of the Python package stack, so it cannot rely on importing other parts of the Python library at initialisation time (except for exceptions through PyO3's `import_exception!` mechanism). -This is because, unlike pure-Python modules, the initialisation of `_accelerate` cannot be done partially, and many components of Qiskit import their accelerators from `_accelerate`. +The Qiskit C extension `qiskit._accelerate` needs to be initialized in a single go. +It is the lowest part of the Python package stack, so it cannot rely on importing other parts of the Python library at initialization time (except for exceptions through PyO3's `import_exception!` mechanism). +This is because, unlike pure-Python modules, the initialization of `_accelerate` cannot be done partially, and many components of Qiskit import their accelerators from `_accelerate`. In general, this should not be too onerous a requirement, but if you violate it, you might see Rust panics on import, and PyO3 should wrap that up into an exception. You might be able to track down the Rust source of the import cycle by running the import with the environment variable `RUST_BACKTRACE=full`. diff --git a/crates/accelerate/src/pauli_exp_val.rs b/crates/accelerate/src/pauli_exp_val.rs index 29f741f6cf46..52a2fc07f81d 100644 --- a/crates/accelerate/src/pauli_exp_val.rs +++ b/crates/accelerate/src/pauli_exp_val.rs @@ -32,7 +32,7 @@ pub fn fast_sum_with_simd(simd: S, values: &[f64]) -> f64 { sum + tail.iter().sum::() } -/// Compute the pauli expectatation value of a statevector without x +/// Compute the pauli expectation value of a statevector without x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] pub fn expval_pauli_no_x( @@ -63,7 +63,7 @@ pub fn expval_pauli_no_x( } } -/// Compute the pauli expectatation value of a statevector with x +/// Compute the pauli expectation value of a statevector with x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] pub fn expval_pauli_with_x( @@ -121,7 +121,7 @@ pub fn expval_pauli_with_x( } } -/// Compute the pauli expectatation value of a density matrix without x +/// Compute the pauli expectation value of a density matrix without x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, /)")] pub fn density_expval_pauli_no_x( @@ -153,7 +153,7 @@ pub fn density_expval_pauli_no_x( } } -/// Compute the pauli expectatation value of a density matrix with x +/// Compute the pauli expectation value of a density matrix with x #[pyfunction] #[pyo3(text_signature = "(data, num_qubits, z_mask, x_mask, phase, x_max, /)")] pub fn density_expval_pauli_with_x( diff --git a/crates/accelerate/src/sabre/sabre_dag.rs b/crates/accelerate/src/sabre/sabre_dag.rs index aa35a5d7942f..d783f3844629 100644 --- a/crates/accelerate/src/sabre/sabre_dag.rs +++ b/crates/accelerate/src/sabre/sabre_dag.rs @@ -27,7 +27,7 @@ pub struct DAGNode { } /// A DAG representation of the logical circuit to be routed. This represents the same dataflow -/// dependences as the Python-space [DAGCircuit], but without any information about _what_ the +/// dependencies as the Python-space [DAGCircuit], but without any information about _what_ the /// operations being performed are. Note that all the qubit references here are to "virtual" /// qubits, that is, the qubits are those specified by the user. This DAG does not need to be /// full-width on the hardware. diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index 808269d8ab90..e0c80f716161 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -421,7 +421,7 @@ fn decompose_dense_inner( ) { if num_qubits == 0 { // It would be safe to `return` here, but if it's unreachable then LLVM is allowed to - // optimise out this branch entirely in release mode, which is good for a ~2% speedup. + // optimize out this branch entirely in release mode, which is good for a ~2% speedup. unreachable!("should not call this with an empty operator") } // Base recursion case. @@ -529,7 +529,7 @@ fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec out }; let write_row = |(i_row, row): (usize, &mut [Complex64])| { - // Doing the initialisation here means that when we're in parallel contexts, we do the + // Doing the initialization here means that when we're in parallel contexts, we do the // zeroing across the whole threadpool. This also seems to give a speed-up in serial // contexts, but I don't understand that. ---Jake row.fill(Complex64::new(0.0, 0.0)); @@ -721,7 +721,7 @@ macro_rules! impl_to_matrix_sparse { // The parallel overhead from splitting a subtask is fairly high (allocating and // potentially growing a couple of vecs), so we're trading off some of Rayon's ability - // to keep threads busy by subdivision with minimising overhead; we're setting the + // to keep threads busy by subdivision with minimizing overhead; we're setting the // chunk size such that the iterator will have as many elements as there are threads. let num_threads = rayon::current_num_threads(); let chunk_size = (side + num_threads - 1) / num_threads; @@ -738,7 +738,7 @@ macro_rules! impl_to_matrix_sparse { // Since we compressed the Paulis by summing equal elements, we're // lower-bounded on the number of elements per row by this value, up to // cancellations. This should be a reasonable trade-off between sometimes - // expandin the vector and overallocation. + // expanding the vector and overallocation. let mut values = Vec::::with_capacity(chunk_size * (num_ops + 1) / 2); let mut indices = Vec::<$int_ty>::with_capacity(chunk_size * (num_ops + 1) / 2); diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index f93eb2a8d99c..e8c572b04039 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -293,7 +293,7 @@ fn __num_basis_gates(basis_b: f64, basis_fidelity: f64, unitary: MatRef) -> c64::new(4.0 * c.cos(), 0.0), c64::new(4.0, 0.0), ]; - // The originial Python had `np.argmax`, which returns the lowest index in case two or more + // The original Python had `np.argmax`, which returns the lowest index in case two or more // values have a common maximum value. // `max_by` and `min_by` return the highest and lowest indices respectively, in case of ties. // So to reproduce `np.argmax`, we use `min_by` and switch the order of the @@ -587,7 +587,7 @@ impl TwoQubitWeylDecomposition { // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D P^T where // P ∈ SO(4), D is diagonal with unit-magnitude elements. // - // We can't use raw `eig` directly because it isn't guaranteed to give us real or othogonal + // We can't use raw `eig` directly because it isn't guaranteed to give us real or orthogonal // eigenvectors. Instead, since `M2` is complex-symmetric, // M2 = A + iB // for real-symmetric `A` and `B`, and as diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs index d8a08080a95c..fe78b290e0f5 100644 --- a/crates/qasm2/src/expr.rs +++ b/crates/qasm2/src/expr.rs @@ -104,7 +104,7 @@ impl From for Op { } } -/// An atom of the operator-precendence expression parsing. This is a stripped-down version of the +/// An atom of the operator-precedence expression parsing. This is a stripped-down version of the /// [Token] and [TokenType] used in the main parser. We can use a data enum here because we do not /// need all the expressive flexibility in expecting and accepting many different token types as /// we do in the main parser; it does not significantly harm legibility to simply do @@ -233,7 +233,7 @@ fn binary_power(op: Op) -> (u8, u8) { /// A subparser used to do the operator-precedence part of the parsing for individual parameter /// expressions. The main parser creates a new instance of this struct for each expression it /// expects, and the instance lives only as long as is required to parse that expression, because -/// it takes temporary resposibility for the [TokenStream] that backs the main parser. +/// it takes temporary responsibility for the [TokenStream] that backs the main parser. pub struct ExprParser<'a> { pub tokens: &'a mut Vec, pub context: &'a mut TokenContext, @@ -504,7 +504,7 @@ impl<'a> ExprParser<'a> { // 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 + // there's almost certainly precision loss from the floating-point representing // 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)))), diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs index f9f674cbc93e..551fd2b7af48 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -21,7 +21,7 @@ //! keyword; the spec technically says that any real number is valid, but in reality that leads to //! weirdness like `200.0e-2` being a valid version specifier. We do things with a custom //! context-dependent match after seeing an `OPENQASM` token, to avoid clashes with the general -//! real-number tokenisation. +//! real-number tokenization. use hashbrown::HashMap; use pyo3::prelude::PyResult; @@ -30,7 +30,7 @@ use std::path::Path; use crate::error::{message_generic, Position, QASM2ParseError}; -/// Tokenised version information data. This is more structured than the real number suggested by +/// Tokenized version information data. This is more structured than the real number suggested by /// the specification. #[derive(Clone, Debug)] pub struct Version { @@ -353,7 +353,7 @@ impl TokenStream { line_buffer: Vec::with_capacity(80), done: false, // The first line is numbered "1", and the first column is "0". The counts are - // initialised like this so the first call to `next_byte` can easily detect that it + // initialized like this so the first call to `next_byte` can easily detect that it // needs to extract the next line. line: 0, col: 0, diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index e4c749841124..f7eceb6aeef4 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -1630,7 +1630,7 @@ impl State { /// Update the parser state with the definition of a particular gate. This does not emit any /// bytecode because not all gate definitions need something passing to Python. For example, - /// the Python parser initialises its state including the built-in gates `U` and `CX`, and + /// the Python parser initializes its state including the built-in gates `U` and `CX`, and /// handles the `qelib1.inc` include specially as well. fn define_gate( &mut self, diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs index 2f817187625d..f5cf2fd4efca 100644 --- a/crates/qasm3/src/build.rs +++ b/crates/qasm3/src/build.rs @@ -69,7 +69,7 @@ impl BuilderState { Err(QASM3ImporterError::new_err("cannot handle consts")) } else if decl.initializer().is_some() { Err(QASM3ImporterError::new_err( - "cannot handle initialised bits", + "cannot handle initialized bits", )) } else { self.add_clbit(py, name_id.clone()) @@ -80,7 +80,7 @@ impl BuilderState { Err(QASM3ImporterError::new_err("cannot handle consts")) } else if decl.initializer().is_some() { Err(QASM3ImporterError::new_err( - "cannot handle initialised registers", + "cannot handle initialized registers", )) } else { match dims { diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index 330805fa2f86..fdd92c43c0bc 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -281,7 +281,7 @@ impl PyCircuitModule { /// Circuit construction context object to provide an easier Rust-space interface for us to /// construct the Python :class:`.QuantumCircuit`. The idea of doing this from Rust space like /// this is that we might steadily be able to move more and more of it into being native Rust as -/// the Rust-space APIs around the internal circuit data stabilise. +/// the Rust-space APIs around the internal circuit data stabilize. pub struct PyCircuit(Py); impl PyCircuit { diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs index e912aecdb875..64afe58991ca 100644 --- a/crates/qasm3/src/expr.rs +++ b/crates/qasm3/src/expr.rs @@ -71,7 +71,7 @@ fn eval_const_int(_py: Python, _ast_symbols: &SymbolTable, expr: &asg::TExpr) -> match expr.expression() { asg::Expr::Literal(asg::Literal::Int(lit)) => Ok(*lit.value() as isize), expr => Err(QASM3ImporterError::new_err(format!( - "unhandled expression type for constant-integer evaluatation: {:?}", + "unhandled expression type for constant-integer evaluation: {:?}", expr ))), } diff --git a/docs/conf.py b/docs/conf.py index b35f5ca64d66..f6bf2faa9a18 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -114,9 +114,9 @@ autosummary_generate = True autosummary_generate_overwrite = False -# The pulse library contains some names that differ only in capitalisation, during the changeover +# The pulse library contains some names that differ only in capitalization, during the changeover # surrounding SymbolPulse. Since these resolve to autosummary filenames that also differ only in -# capitalisation, this causes problems when the documentation is built on an OS/filesystem that is +# capitalization, this causes problems when the documentation is built on an OS/filesystem that is # enforcing case-insensitive semantics. This setting defines some custom names to prevent the clash # from happening. autosummary_filename_map = { diff --git a/qiskit/_numpy_compat.py b/qiskit/_numpy_compat.py index a6c06671c986..9b6b466fbc9c 100644 --- a/qiskit/_numpy_compat.py +++ b/qiskit/_numpy_compat.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Compatiblity helpers for the Numpy 1.x to 2.0 transition.""" +"""Compatibility helpers for the Numpy 1.x to 2.0 transition.""" import re import typing diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 43087760153e..65a88519a0de 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -270,7 +270,7 @@ * :class:`ContinueLoopOp`, to move immediately to the next iteration of the containing loop * :class:`ForLoopOp`, to loop over a fixed range of values * :class:`IfElseOp`, to conditionally enter one of two subcircuits - * :class:`SwitchCaseOp`, to conditionally enter one of many subcicuits + * :class:`SwitchCaseOp`, to conditionally enter one of many subcircuits * :class:`WhileLoopOp`, to repeat a subcircuit until a condition is falsified. :ref:`Circuits can include classical expressions that are evaluated in real time diff --git a/qiskit/circuit/_classical_resource_map.py b/qiskit/circuit/_classical_resource_map.py index bff7d9f80fec..ba42f15cddc9 100644 --- a/qiskit/circuit/_classical_resource_map.py +++ b/qiskit/circuit/_classical_resource_map.py @@ -31,7 +31,7 @@ class VariableMapper(expr.ExprVisitor[expr.Expr]): call its :meth:`map_condition`, :meth:`map_target` or :meth:`map_expr` methods as appropriate, which will return the new object that should be used. - If an ``add_register`` callable is given to the initialiser, the mapper will use it to attempt + If an ``add_register`` callable is given to the initializer, the mapper will use it to attempt to add new aliasing registers to the outer circuit object, if there is not already a suitable register for the mapping available in the circuit. If this parameter is not given, a ``ValueError`` will be raised instead. The given ``add_register`` callable may choose to raise @@ -73,12 +73,12 @@ def _map_register(self, theirs: ClassicalRegister) -> ClassicalRegister: def map_condition(self, condition, /, *, allow_reorder=False): """Map the given ``condition`` so that it only references variables in the destination - circuit (as given to this class on initialisation). + circuit (as given to this class on initialization). If ``allow_reorder`` is ``True``, then when a legacy condition (the two-tuple form) is made on a register that has a counterpart in the destination with all the same (mapped) bits but in a different order, then that register will be used and the value suitably modified to - make the equality condition work. This is maintaining legacy (tested) behaviour of + make the equality condition work. This is maintaining legacy (tested) behavior of :meth:`.DAGCircuit.compose`; nowhere else does this, and in general this would require *far* more complex classical rewriting than Terra needs to worry about in the full expression era. """ @@ -91,7 +91,7 @@ def map_condition(self, condition, /, *, allow_reorder=False): return (self.bit_map[target], value) if not allow_reorder: return (self._map_register(target), value) - # This is maintaining the legacy behaviour of `DAGCircuit.compose`. We don't attempt to + # This is maintaining the legacy behavior of `DAGCircuit.compose`. We don't attempt to # speed-up this lookup with a cache, since that would just make the more standard cases more # annoying to deal with. mapped_bits_order = [self.bit_map[bit] for bit in target] @@ -114,7 +114,7 @@ def map_condition(self, condition, /, *, allow_reorder=False): def map_target(self, target, /): """Map the real-time variables in a ``target`` of a :class:`.SwitchCaseOp` to the new - circuit, as defined in the ``circuit`` argument of the initialiser of this class.""" + circuit, as defined in the ``circuit`` argument of the initializer of this class.""" if isinstance(target, Clbit): return self.bit_map[target] if isinstance(target, ClassicalRegister): diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index 62b6829ce4a7..586b06ec9dbc 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -53,7 +53,7 @@ class Expr(abc.ABC): expressions, and it does not make sense to add more outside of Qiskit library code. All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and - should not call the parent initialiser.""" + should not call the parent initializer.""" __slots__ = ("type",) @@ -193,7 +193,7 @@ def __copy__(self): return self def __deepcopy__(self, memo): - # ... as are all my consituent parts. + # ... as are all my constituent parts. return self @@ -241,7 +241,7 @@ class Op(enum.Enum): # If adding opcodes, remember to add helper constructor functions in `constructors.py`. # The opcode integers should be considered a public interface; they are used by - # serialisation formats that may transfer data between different versions of Qiskit. + # serialization formats that may transfer data between different versions of Qiskit. BIT_NOT = 1 """Bitwise negation. ``~operand``.""" LOGIC_NOT = 2 @@ -309,7 +309,7 @@ class Op(enum.Enum): # If adding opcodes, remember to add helper constructor functions in `constructors.py` # The opcode integers should be considered a public interface; they are used by - # serialisation formats that may transfer data between different versions of Qiskit. + # serialization formats that may transfer data between different versions of Qiskit. BIT_AND = 1 """Bitwise "and". ``lhs & rhs``.""" BIT_OR = 2 diff --git a/qiskit/circuit/classical/expr/visitors.py b/qiskit/circuit/classical/expr/visitors.py index 744257714b79..be7e9311c377 100644 --- a/qiskit/circuit/classical/expr/visitors.py +++ b/qiskit/circuit/classical/expr/visitors.py @@ -29,7 +29,7 @@ class ExprVisitor(typing.Generic[_T_co]): """Base class for visitors to the :class:`Expr` tree. Subclasses should override whichever of - the ``visit_*`` methods that they are able to handle, and should be organised such that + the ``visit_*`` methods that they are able to handle, and should be organized such that non-existent methods will never be called.""" # The method names are self-explanatory and docstrings would just be noise. diff --git a/qiskit/circuit/classical/types/__init__.py b/qiskit/circuit/classical/types/__init__.py index 14365fd32a6f..ae38a0d97fb5 100644 --- a/qiskit/circuit/classical/types/__init__.py +++ b/qiskit/circuit/classical/types/__init__.py @@ -40,7 +40,7 @@ .. autoclass:: Bool .. autoclass:: Uint -Note that :class:`Uint` defines a family of types parametrised by their width; it is not one single +Note that :class:`Uint` defines a family of types parametrized by their width; it is not one single type, which may be slightly different to the 'classical' programming languages you are used to. diff --git a/qiskit/circuit/classical/types/types.py b/qiskit/circuit/classical/types/types.py index 04266aefd410..d20e7b5fd746 100644 --- a/qiskit/circuit/classical/types/types.py +++ b/qiskit/circuit/classical/types/types.py @@ -29,7 +29,7 @@ class _Singleton(type): - """Metaclass to make the child, which should take zero initialisation arguments, a singleton + """Metaclass to make the child, which should take zero initialization arguments, a singleton object.""" def _get_singleton_instance(cls): @@ -76,7 +76,7 @@ def __deepcopy__(self, _memo): def __setstate__(self, state): _dict, slots = state for slot, value in slots.items(): - # We need to overcome the type's enforcement of immutability post initialisation. + # We need to overcome the type's enforcement of immutability post initialization. super().__setattr__(slot, value) diff --git a/qiskit/circuit/controlflow/_builder_utils.py b/qiskit/circuit/controlflow/_builder_utils.py index bfb0d905387e..e80910aac3ed 100644 --- a/qiskit/circuit/controlflow/_builder_utils.py +++ b/qiskit/circuit/controlflow/_builder_utils.py @@ -127,7 +127,7 @@ def unify_circuit_resources(circuits: Iterable[QuantumCircuit]) -> Iterable[Quan This function will preferentially try to mutate its inputs if they share an ordering, but if not, it will rebuild two new circuits. This is to avoid coupling too tightly to the inner class; there is no real support for deleting or re-ordering bits within a :obj:`.QuantumCircuit` - context, and we don't want to rely on the *current* behaviour of the private APIs, since they + context, and we don't want to rely on the *current* behavior of the private APIs, since they are very liable to change. No matter the method used, circuits with unified bits and registers are returned. """ diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index bb0a30ea6af6..ab464a50ca67 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -13,7 +13,7 @@ """Builder types for the basic control-flow constructs.""" # This file is in circuit.controlflow rather than the root of circuit because the constructs here -# are only intended to be localised to constructing the control flow instructions. We anticipate +# are only intended to be localized to constructing the control flow instructions. We anticipate # having a far more complete builder of all circuits, with more classical control and creation, in # the future. @@ -206,7 +206,7 @@ class InstructionPlaceholder(Instruction, abc.ABC): When appending a placeholder instruction into a circuit scope, you should create the placeholder, and then ask it what resources it should be considered as using from the start by calling :meth:`.InstructionPlaceholder.placeholder_instructions`. This set will be a subset of - the final resources it asks for, but it is used for initialising resources that *must* be + the final resources it asks for, but it is used for initializing resources that *must* be supplied, such as the bits used in the conditions of placeholder ``if`` statements. .. warning:: @@ -360,7 +360,7 @@ def __init__( which use a classical register as their condition. allow_jumps: Whether this builder scope should allow ``break`` and ``continue`` statements within it. This is intended to help give sensible error messages when - dangerous behaviour is encountered, such as using ``break`` inside an ``if`` context + dangerous behavior is encountered, such as using ``break`` inside an ``if`` context manager that is not within a ``for`` manager. This can only be safe if the user is going to place the resulting :obj:`.QuantumCircuit` inside a :obj:`.ForLoopOp` that uses *exactly* the same set of resources. We cannot verify this from within the @@ -395,7 +395,7 @@ def clbits(self): def allow_jumps(self): """Whether this builder scope should allow ``break`` and ``continue`` statements within it. - This is intended to help give sensible error messages when dangerous behaviour is + This is intended to help give sensible error messages when dangerous behavior is encountered, such as using ``break`` inside an ``if`` context manager that is not within a ``for`` manager. This can only be safe if the user is going to place the resulting :obj:`.QuantumCircuit` inside a :obj:`.ForLoopOp` that uses *exactly* the same set of diff --git a/qiskit/circuit/controlflow/if_else.py b/qiskit/circuit/controlflow/if_else.py index 121d4c681f27..dd639c65f4b5 100644 --- a/qiskit/circuit/controlflow/if_else.py +++ b/qiskit/circuit/controlflow/if_else.py @@ -199,7 +199,7 @@ def __init__( super().__init__( "if_else", len(self.__resources.qubits), len(self.__resources.clbits), [], label=label ) - # Set the condition after super().__init__() has initialised it to None. + # Set the condition after super().__init__() has initialized it to None. self.condition = validate_condition(condition) def with_false_block(self, false_block: ControlFlowBuilderBlock) -> "IfElsePlaceholder": @@ -236,7 +236,7 @@ def registers(self): def _calculate_placeholder_resources(self) -> InstructionResources: """Get the placeholder resources (see :meth:`.placeholder_resources`). - This is a separate function because we use the resources during the initialisation to + This is a separate function because we use the resources during the initialization to determine how we should set our ``num_qubits`` and ``num_clbits``, so we implement the public version as a cache access for efficiency. """ diff --git a/qiskit/circuit/controlflow/switch_case.py b/qiskit/circuit/controlflow/switch_case.py index 446230c3c3cd..6df8c4ef62ab 100644 --- a/qiskit/circuit/controlflow/switch_case.py +++ b/qiskit/circuit/controlflow/switch_case.py @@ -94,7 +94,7 @@ def __init__( it's easier for things like `assign_parameters`, which need to touch each circuit object exactly once, to function.""" self._label_spec: List[Tuple[Union[int, Literal[CASE_DEFAULT]], ...]] = [] - """List of the normalised jump value specifiers. This is a list of tuples, where each tuple + """List of the normalized jump value specifiers. This is a list of tuples, where each tuple contains the values, and the indexing is the same as the values of `_case_map` and `_params`.""" self._params = [] diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index f53c5b9e9b3c..1b5fca3f738b 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -115,12 +115,12 @@ def base_class(self) -> Type[Instruction]: The "base class" of an instruction is the lowest class in its inheritance tree that the object should be considered entirely compatible with for _all_ circuit applications. This typically means that the subclass is defined purely to offer some sort of programmer - convenience over the base class, and the base class is the "true" class for a behavioural + convenience over the base class, and the base class is the "true" class for a behavioral perspective. In particular, you should *not* override :attr:`base_class` if you are defining a custom version of an instruction that will be implemented differently by - hardware, such as an alternative measurement strategy, or a version of a parametrised gate + hardware, such as an alternative measurement strategy, or a version of a parametrized gate with a particular set of parameters for the purposes of distinguishing it in a - :class:`.Target` from the full parametrised gate. + :class:`.Target` from the full parametrized gate. This is often exactly equivalent to ``type(obj)``, except in the case of singleton instances of standard-library instructions. These singleton instances are special subclasses of their diff --git a/qiskit/circuit/library/arithmetic/linear_amplitude_function.py b/qiskit/circuit/library/arithmetic/linear_amplitude_function.py index 0825f3f4e0a9..a79670eef654 100644 --- a/qiskit/circuit/library/arithmetic/linear_amplitude_function.py +++ b/qiskit/circuit/library/arithmetic/linear_amplitude_function.py @@ -119,7 +119,7 @@ def __init__( self._image = image self._rescaling_factor = rescaling_factor - # do rescalings + # do rescaling a, b = domain c, d = image diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 25f6c27bbe1b..2a750195dab3 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -162,7 +162,7 @@ def __init__( self._bounds: list[tuple[float | None, float | None]] | None = None self._flatten = flatten - # During the build, if a subclass hasn't overridden our parametrisation methods, we can use + # During the build, if a subclass hasn't overridden our parametrization methods, we can use # a newer fast-path method to parametrise the rotation and entanglement blocks if internally # those are just simple stdlib gates that have been promoted to circuits. We don't # precalculate the fast-path layers themselves because there's far too much that can be @@ -1093,7 +1093,7 @@ def _stdlib_gate_from_simple_block(block: QuantumCircuit) -> _StdlibGateResult | return None instruction = block.data[0] # If the single instruction isn't a standard-library gate that spans the full width of the block - # in the correct order, we're not simple. If the gate isn't fully parametrised with pure, + # in the correct order, we're not simple. If the gate isn't fully parametrized with pure, # unique `Parameter` instances (expressions are too complex) that are in order, we're not # simple. if ( diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index 3495bc180f08..07684097f8cc 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -183,7 +183,7 @@ def __setitem__(self, key, value): # Magic numbers: CUGate has 4 parameters, UGate has 3, with the last of CUGate's missing. if isinstance(key, slice): # We don't need to worry about the case of the slice being used to insert extra / remove - # elements because that would be "undefined behaviour" in a gate already, so we're + # elements because that would be "undefined behavior" in a gate already, so we're # within our rights to do anything at all. for i, base_key in enumerate(range(*key.indices(4))): if base_key < 0: diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 6e959b3e62cb..cd4a61963823 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -972,7 +972,7 @@ def __init__( _singleton_lookup_key = stdlib_singleton_key(num_ctrl_qubits=4) - # seems like open controls not hapening? + # seems like open controls not happening? def _define(self): """ gate c3sqrtx a,b,c,d diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py index 825679f7d4f5..c7a8228dd463 100644 --- a/qiskit/circuit/parameter.py +++ b/qiskit/circuit/parameter.py @@ -62,7 +62,7 @@ class Parameter(ParameterExpression): __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 + # `_parameter_symbols` dictionary we need to pass to it before we're entirely initialized # anyway, because `ParameterExpression` depends heavily on the structure of `Parameter`. def __init__( diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 7f839677b904..16b691480d26 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -48,7 +48,7 @@ def __init__(self, symbol_map: dict, expr): expr (sympy.Expr): Expression of :class:`sympy.Symbol` s. """ # NOTE: `Parameter.__init__` does not call up to this method, since this method is dependent - # on `Parameter` instances already being initialised enough to be hashable. If changing + # on `Parameter` instances already being initialized enough to be hashable. If changing # this method, check that `Parameter.__init__` and `__setstate__` are still valid. self._parameter_symbols = symbol_map self._parameter_keys = frozenset(p._hash_key() for p in self._parameter_symbols) @@ -421,8 +421,8 @@ def __float__(self): ) from None # In symengine, if an expression was complex at any time, its type is likely to have # stayed "complex" even when the imaginary part symbolically (i.e. exactly) - # cancelled out. Sympy tends to more aggressively recognise these as symbolically - # real. This second attempt at a cast is a way of unifying the behaviour to the + # cancelled out. Sympy tends to more aggressively recognize these as symbolically + # real. This second attempt at a cast is a way of unifying the behavior to the # more expected form for our users. cval = complex(self) if cval.imag == 0.0: diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 010a91e3639e..a88dfd43ea4b 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -111,7 +111,7 @@ # # 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. +# it has at least some amount of organizational structure. class QuantumCircuit: @@ -369,7 +369,7 @@ class QuantumCircuit: ------------------------------- 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 + contexts. If you have got a :class:`.Bit` instance and a circuit, 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. @@ -650,7 +650,7 @@ class QuantumCircuit: 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. + since their exact output is often a synthesized version of a gate. =============================== ================================================= :class:`QuantumCircuit` method Base :mod:`qiskit.circuit.library` :class:`.Gate` @@ -2500,7 +2500,7 @@ def _append(self, instruction, qargs=(), cargs=(), *, _standard_gate: bool = Fal and the only reference to the circuit the instructions are being appended to is within that same function. In particular, it is not safe to call :meth:`QuantumCircuit._append` on a circuit that is received by a function argument. - This is because :meth:`.QuantumCircuit._append` will not recognise the scoping + This is because :meth:`.QuantumCircuit._append` will not recognize the scoping constructs of the control-flow builder interface. Args: @@ -2582,7 +2582,7 @@ def get_parameter(self, name: str, default: typing.Any = ...) -> Parameter: my_param = Parameter("my_param") - # Create a parametrised circuit. + # Create a parametrized circuit. qc = QuantumCircuit(1) qc.rx(my_param, 0) @@ -2798,8 +2798,8 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V # two classical registers we measured into above. qc.add_var(my_var, expr.bit_and(cr1, cr2)) """ - # Validate the initialiser first to catch cases where the variable to be declared is being - # used in the initialiser. + # Validate the initializer first to catch cases where the variable to be declared is being + # used in the initializer. circuit_scope = self._current_scope() # Convenience method to widen Python integer literals to the right width during the initial # lift, if the type is already known via the variable. @@ -2823,7 +2823,7 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V var = name_or_var circuit_scope.add_uninitialized_var(var) try: - # Store is responsible for ensuring the type safety of the initialisation. + # Store is responsible for ensuring the type safety of the initialization. store = Store(var, initial) except CircuitError: circuit_scope.remove_var(var) @@ -2853,7 +2853,7 @@ def add_uninitialized_var(self, var: expr.Var, /): # name, and to be a bit less ergonomic than `add_var` (i.e. not allowing the (name, type) # overload) to discourage people from using it when they should use `add_var`. # - # This function exists so that there is a method to emulate `copy_empty_like`'s behaviour of + # This function exists so that there is a method to emulate `copy_empty_like`'s behavior of # adding uninitialised variables, which there's no obvious way around. We need to be sure # that _some_ sort of handling of uninitialised variables is taken into account in our # structures, so that doesn't become a huge edge case, even though we make no assertions @@ -2887,7 +2887,7 @@ def add_capture(self, var: expr.Var): """ if self._control_flow_scopes: # Allow manual capturing. Not sure why it'd be useful, but there's a clear expected - # behaviour here. + # behavior here. self._control_flow_scopes[-1].use_var(var) return if self._vars_input: @@ -3656,7 +3656,7 @@ def copy_empty_like( if vars_mode == "alike": # Note that this causes the local variables to be uninitialised, because the stores are # not copied. This can leave the circuit in a potentially dangerous state for users if - # they don't re-add initialiser stores. + # they don't re-add initializer stores. cpy._vars_local = self._vars_local.copy() cpy._vars_input = self._vars_input.copy() cpy._vars_capture = self._vars_capture.copy() @@ -4061,7 +4061,7 @@ def global_phase(self, angle: ParameterValueType): angle (float, ParameterExpression): radians """ # If we're currently parametric, we need to throw away the references. This setter is - # called by some subclasses before the inner `_global_phase` is initialised. + # called by some subclasses before the inner `_global_phase` is initialized. if isinstance(getattr(self._data, "global_phase", None), ParameterExpression): self._parameters = None if isinstance(angle, ParameterExpression): @@ -4273,7 +4273,7 @@ def assign_parameters( # pylint: disable=missing-raises-doc target._increment_instances() target._name_update() - # Normalise the inputs into simple abstract interfaces, so we've dispatched the "iteration" + # Normalize the inputs into simple abstract interfaces, so we've dispatched the "iteration" # logic in one place at the start of the function. This lets us do things like calculate # and cache expensive properties for (e.g.) the sequence format only if they're used; for # many large, close-to-hardware circuits, we won't need the extra handling for @@ -5909,7 +5909,7 @@ def _pop_scope(self) -> ControlFlowBuilderBlock: """Finish a scope used in the control-flow builder interface, and return it to the caller. This should only be done by the control-flow context managers, since they naturally - synchronise the creation and deletion of stack elements.""" + synchronize the creation and deletion of stack elements.""" return self._control_flow_scopes.pop() def _peek_previous_instruction_in_scope(self) -> CircuitInstruction: diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index f27cbfbfca88..3bcdbeaef4ac 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -193,7 +193,8 @@ def random_circuit( # Apply arbitrary random operations in layers across all qubits. for layer_number in range(depth): # We generate all the randomness for the layer in one go, to avoid many separate calls to - # the randomisation routines, which can be fairly slow. + # the randomization routines, which can be fairly slow. + # This reliably draws too much randomness, but it's less expensive than looping over more # calls to the rng. After, trim it down by finding the point when we've used all the qubits. @@ -239,9 +240,9 @@ def random_circuit( if not gate_added_flag: break - # For efficiency in the Python loop, this uses Numpy vectorisation to pre-calculate the + # For efficiency in the Python loop, this uses Numpy vectorization to pre-calculate the # indices into the lists of qubits and parameters for every gate, and then suitably - # randomises those lists. + # randomizes those lists. q_indices = np.empty(len(gate_specs) + 1, dtype=np.int64) p_indices = np.empty(len(gate_specs) + 1, dtype=np.int64) q_indices[0] = p_indices[0] = 0 diff --git a/qiskit/circuit/singleton.py b/qiskit/circuit/singleton.py index bd689b6be103..874b979ff588 100644 --- a/qiskit/circuit/singleton.py +++ b/qiskit/circuit/singleton.py @@ -42,7 +42,7 @@ heart of Qiskit's data model for circuits. From a library-author perspective, the minimum that is needed to enhance a :class:`.Gate` or -:class:`~.circuit.Instruction` with this behaviour is to inherit from :class:`SingletonGate` +:class:`~.circuit.Instruction` with this behavior is to inherit from :class:`SingletonGate` (:class:`SingletonInstruction`) instead of :class:`.Gate` (:class:`~.circuit.Instruction`), and for the ``__init__`` method to have defaults for all of its arguments (these will be the state of the singleton instance). For example:: @@ -175,7 +175,7 @@ def _singleton_lookup_key(n=1, label=None): This section is primarily developer documentation for the code; none of the machinery described here is public, and it is not safe to inherit from any of it directly. -There are several moving parts to tackle here. The behaviour of having ``XGate()`` return some +There are several moving parts to tackle here. The behavior of having ``XGate()`` return some singleton object that is an (inexact) instance of :class:`.XGate` but *without* calling ``__init__`` requires us to override :class:`type.__call__ `. This means that :class:`.XGate` must have a metaclass that defines ``__call__`` to return the singleton instance. @@ -484,7 +484,7 @@ class they are providing overrides for has more lazy attributes or user-exposed instruction._define() # We use this `list` subclass that rejects all mutation rather than a simple `tuple` because # the `params` typing is specified as `list`. Various places in the library and beyond do - # `x.params.copy()` when they want to produce a version they own, which is good behaviour, + # `x.params.copy()` when they want to produce a version they own, which is good behavior, # and would fail if we switched to a `tuple`, which has no `copy` method. instruction._params = _frozenlist(instruction._params) return instruction diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 571e330eb0db..4d0570542b03 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -62,7 +62,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None if circuit.num_input_vars: # This could be supported by moving the `input` variables to be parameters of the - # instruction, but we don't really have a good reprssentation of that yet, so safer to + # instruction, but we don't really have a good representation of that yet, so safer to # forbid it. raise QiskitError("Circuits with 'input' variables cannot yet be converted to instructions") if circuit.num_captured_vars: diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 686951f26fc2..d14340a8cb9b 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1525,7 +1525,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit )[0] self._multi_graph.add_edge(pred._node_id, succ._node_id, contracted_var) - # Exlude any nodes from in_dag that are not a DAGOpNode or are on + # Exclude any nodes from in_dag that are not a DAGOpNode or are on # wires outside the set specified by the wires kwarg def filter_fn(node): if not isinstance(node, DAGOpNode): @@ -1615,7 +1615,7 @@ def substitute_node(self, node: DAGOpNode, op, inplace: bool = False, propagate_ be used. propagate_condition (bool): Optional, default True. If True, a condition on the ``node`` to be replaced will be applied to the new ``op``. This is the legacy - behaviour. If either node is a control-flow operation, this will be ignored. If + behavior. If either node is a control-flow operation, this will be ignored. If the ``op`` already has a condition, :exc:`.DAGCircuitError` is raised. Returns: diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index 99527bf584ee..85f422f181b9 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -225,7 +225,7 @@ def callback_func(**kwargs): in_programs = [in_programs] is_list = False - # If we're not going to run in parallel, we want to avoid spending time `dill` serialising + # If we're not going to run in parallel, we want to avoid spending time `dill` serializing # ourselves, since that can be quite expensive. if len(in_programs) == 1 or not should_run_in_parallel(num_processes): out = [ @@ -242,7 +242,7 @@ def callback_func(**kwargs): # Pass manager may contain callable and we need to serialize through dill rather than pickle. # See https://github.com/Qiskit/qiskit-terra/pull/3290 # Note that serialized object is deserialized as a different object. - # Thus, we can resue the same manager without state collision, without building it per thread. + # Thus, we can reuse the same manager without state collision, without building it per thread. return parallel_map( _run_workflow_in_new_process, values=in_programs, diff --git a/qiskit/primitives/base/base_estimator.py b/qiskit/primitives/base/base_estimator.py index 9191b4162d92..0a7c0ec86289 100644 --- a/qiskit/primitives/base/base_estimator.py +++ b/qiskit/primitives/base/base_estimator.py @@ -203,7 +203,7 @@ class BaseEstimatorV2(ABC): @staticmethod def _make_data_bin(_: EstimatorPub) -> type[DataBin]: - # this method is present for backwards compat. new primitive implementatinos + # this method is present for backwards compat. new primitive implementations # should avoid it. return DataBin diff --git a/qiskit/primitives/containers/bindings_array.py b/qiskit/primitives/containers/bindings_array.py index 6ab60f4771db..89730e5ce94c 100644 --- a/qiskit/primitives/containers/bindings_array.py +++ b/qiskit/primitives/containers/bindings_array.py @@ -95,7 +95,7 @@ def __init__( be inferred from the provided arrays. Ambiguity arises whenever the key of an entry of ``data`` contains only one parameter and the corresponding array's shape ends in a one. In this case, it can't be decided whether that one is an index over parameters, or whether - it should be encorporated in :attr:`~shape`. + it should be incorporated in :attr:`~shape`. Since :class:`~.Parameter` objects are only allowed to represent float values, this class casts all given values to float. If an incompatible dtype is given, such as complex @@ -131,7 +131,7 @@ class casts all given values to float. If an incompatible dtype is given, such a def __getitem__(self, args) -> BindingsArray: # because the parameters live on the last axis, we don't need to do anything special to - # accomodate them because there will always be an implicit slice(None, None, None) + # accommodate them because there will always be an implicit slice(None, None, None) # on all unspecified trailing dimensions # separately, we choose to not disallow args which touch the last dimension, even though it # would not be a particularly friendly way to chop parameters diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index ed9fd3fdbb87..931dbed479ec 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -86,7 +86,7 @@ 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 + methods; changing that behavior would have annoying knock-on effects through all the rest of the documentation, so instead we just hard-code the automethod directive. """ self._configuration = configuration diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py index fe4e7303a674..4d716fb372bb 100644 --- a/qiskit/providers/options.py +++ b/qiskit/providers/options.py @@ -116,7 +116,7 @@ def __len__(self): def __setitem__(self, key, value): self.update_options(**{key: value}) - # backwards-compatibilty with Qiskit Experiments: + # backwards-compatibility with Qiskit Experiments: @property def __dict__(self): diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index 5a2f189c4102..f17fe29113e2 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -20,7 +20,7 @@ .. note:: - OpenQASM 2 is a simple language, and not suitable for general serialisation of Qiskit objects. + OpenQASM 2 is a simple language, and not suitable for general serialization of Qiskit objects. See :ref:`some discussion of alternatives below `, if that is what you are looking for. @@ -95,7 +95,7 @@ Exporting API ============= -Similar to other serialisation modules in Python, this module offers two public functions: +Similar to other serialization modules in Python, this module offers two public functions: :func:`dump` and :func:`dumps`, which take a :class:`.QuantumCircuit` and write out a representative OpenQASM 2 program to a file-like object or return a string, respectively. @@ -394,7 +394,7 @@ def add_one(x): :meth:`.QuantumCircuit.from_qasm_str` and :meth:`~.QuantumCircuit.from_qasm_file` used to make a few additions on top of the raw specification. Qiskit originally tried to use OpenQASM 2 as a sort of -serialisation format, and expanded its behaviour as Qiskit expanded. The new parser under all its +serialization format, and expanded its behavior as Qiskit expanded. The new parser under all its defaults implements the specification more strictly. In particular, in the legacy importers: @@ -445,11 +445,11 @@ def add_one(x): * the parsed grammar is effectively the same as :ref:`the strict mode of the new importers `. -You can emulate this behaviour in :func:`load` and :func:`loads` by setting `include_path` +You can emulate this behavior in :func:`load` and :func:`loads` by setting `include_path` appropriately (try inspecting the variable ``qiskit.__file__`` to find the installed location), and by passing a list of :class:`CustomInstruction` instances for each of the custom gates you care about. To make things easier we make three tuples available, which each contain one component of -a configuration that is equivalent to Qiskit's legacy converter behaviour. +a configuration that is equivalent to Qiskit's legacy converter behavior. .. py:data:: LEGACY_CUSTOM_INSTRUCTIONS @@ -473,7 +473,7 @@ def add_one(x): instruction, it does not matter how the gates are actually defined and used, the legacy importer will always attempt to output its custom objects for them. This can result in errors during the circuit construction, even after a successful parse. There is no way to emulate this buggy -behaviour with :mod:`qiskit.qasm2`; only an ``include "qelib1.inc";`` statement or the +behavior with :mod:`qiskit.qasm2`; only an ``include "qelib1.inc";`` statement or the `custom_instructions` argument can cause built-in Qiskit instructions to be used, and the signatures of these match each other. @@ -549,7 +549,7 @@ def add_one(x): def _normalize_path(path: Union[str, os.PathLike]) -> str: - """Normalise a given path into a path-like object that can be passed to Rust. + """Normalize a given path into a path-like object that can be passed to Rust. Ideally this would be something that we can convert to Rust's `OSString`, but in practice, Python uses `os.fsencode` to produce a `bytes` object, but this doesn't map especially well. diff --git a/qiskit/qasm2/export.py b/qiskit/qasm2/export.py index 46471fa087b2..3cf0d8942553 100644 --- a/qiskit/qasm2/export.py +++ b/qiskit/qasm2/export.py @@ -308,7 +308,7 @@ def _define_custom_operation(operation, gates_to_define): lib.U3Gate, } - # In known-good situations we want to use a manually parametrised object as the source of the + # In known-good situations we want to use a manually parametrized object as the source of the # definition, but still continue to return the given object as the call-site object. if operation.base_class in known_good_parameterized: parameterized_operation = type(operation)(*_FIXED_PARAMETERS[: len(operation.params)]) diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py index 30c85843a361..a40270a99b8c 100644 --- a/qiskit/qasm2/parse.py +++ b/qiskit/qasm2/parse.py @@ -287,7 +287,7 @@ def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]): class _DefinedGate(Gate): """A gate object defined by a `gate` statement in an OpenQASM 2 program. This object lazily - binds its parameters to its definition, so it is only synthesised when required.""" + binds its parameters to its definition, so it is only synthesized when required.""" def __init__(self, name, num_qubits, params, gates, bytecode): self._gates = gates diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py index 300c53900d43..0bae60144afc 100644 --- a/qiskit/qasm3/ast.py +++ b/qiskit/qasm3/ast.py @@ -317,7 +317,7 @@ def __init__(self, expression: Expression): class ClassicalDeclaration(Statement): - """Declaration of a classical type, optionally initialising it to a value.""" + """Declaration of a classical type, optionally initializing it to a value.""" def __init__(self, type_: ClassicalType, identifier: Identifier, initializer=None): self.type = type_ diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index 78b992b17cf2..6d5344bcc255 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -467,7 +467,7 @@ def build_program(self): self.build_gate_definition(instruction) for instruction in gates_to_declare ] - # Early IBM runtime paramterisation uses unbound `Parameter` instances as `input` variables, + # Early IBM runtime parametrization uses unbound `Parameter` instances as `input` variables, # not the explicit realtime `Var` variables, so we need this explicit scan. self.hoist_global_parameter_declarations() # Qiskit's clbits and classical registers need to get mapped to implicit OQ3 variables, but @@ -681,7 +681,7 @@ def hoist_classical_register_declarations(self): doesn't involve the declaration of *new* bits or registers in inner scopes; only the :class:`.expr.Var` mechanism allows that. - The behaviour of this function depends on the setting ``allow_aliasing``. If this + The behavior of this function depends on the setting ``allow_aliasing``. If this is ``True``, then the output will be in the same form as the output of :meth:`.build_classical_declarations`, with the registers being aliases. If ``False``, it will instead return a :obj:`.ast.ClassicalDeclaration` for each classical register, and one @@ -942,7 +942,7 @@ def case(values, case_block): ), ] - # Handle the stabilised syntax. + # Handle the stabilized syntax. cases = [] default = None for values, block in instruction.operation.cases_specifier(): diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 11374e9aca92..8f34ee0855ac 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -89,7 +89,7 @@ class InstructionToQobjConverter: The transfer layer format must be the text representation that coforms to the `OpenPulse specification`__. Extention to the OpenPulse can be achieved by subclassing this this with - extra methods corresponding to each augumented instruction. For example, + extra methods corresponding to each augmented instruction. For example, .. code-block:: python @@ -503,7 +503,7 @@ class QobjToInstructionConverter: The transfer layer format must be the text representation that coforms to the `OpenPulse specification`__. Extention to the OpenPulse can be achieved by subclassing this this with - extra methods corresponding to each augumented instruction. For example, + extra methods corresponding to each augmented instruction. For example, .. code-block:: python diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 1a3393b1e4c8..eae5e6f57ad9 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -522,7 +522,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen metadata_deserializer (JSONDecoder): An optional JSONDecoder class that will be used for the ``cls`` kwarg on the internal ``json.load`` call used to deserialize the JSON payload used for - the :attr:`.ScheduleBlock.metadata` attribute for a schdule block + the :attr:`.ScheduleBlock.metadata` attribute for a schedule block in the file-like object. If this is not specified the circuit metadata will be parsed as JSON with the stdlib ``json.load()`` function using the default ``JSONDecoder`` class. diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py index 3ff6b4a35af3..60262440d033 100644 --- a/qiskit/qpy/type_keys.py +++ b/qiskit/qpy/type_keys.py @@ -159,7 +159,7 @@ class Condition(IntEnum): """Type keys for the ``conditional_key`` field of the INSTRUCTION struct.""" # This class is deliberately raw integers and not in terms of ASCII characters for backwards - # compatiblity in the form as an old Boolean value was expanded; `NONE` and `TWO_TUPLE` must + # compatibility in the form as an old Boolean value was expanded; `NONE` and `TWO_TUPLE` must # have the enumeration values 0 and 1. NONE = 0 @@ -276,7 +276,7 @@ class ScheduleInstruction(TypeKeyBase): REFERENCE = b"y" # 's' is reserved by ScheduleBlock, i.e. block can be nested as an element. - # Call instructon is not supported by QPY. + # Call instruction is not supported by QPY. # This instruction has been excluded from ScheduleBlock instructions with # qiskit-terra/#8005 and new instruction Reference will be added instead. # Call is only applied to Schedule which is not supported by QPY. diff --git a/qiskit/quantum_info/operators/channel/transformations.py b/qiskit/quantum_info/operators/channel/transformations.py index 18987e5e943c..8f429cad8cea 100644 --- a/qiskit/quantum_info/operators/channel/transformations.py +++ b/qiskit/quantum_info/operators/channel/transformations.py @@ -228,7 +228,7 @@ def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): # This should be a call to la.eigh, but there is an OpenBlas # threading issue that is causing segfaults. # Need schur here since la.eig does not - # guarentee orthogonality in degenerate subspaces + # guarantee orthogonality in degenerate subspaces w, v = la.schur(data, output="complex") w = w.diagonal().real # Check eigenvalues are non-negative diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py index 75b455410f49..bcd9f6b094ae 100644 --- a/qiskit/quantum_info/operators/dihedral/dihedral.py +++ b/qiskit/quantum_info/operators/dihedral/dihedral.py @@ -97,7 +97,7 @@ class CNOTDihedral(BaseOperator, AdjointMixin): with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ @@ -325,7 +325,7 @@ def to_circuit(self): with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ # pylint: disable=cyclic-import diff --git a/qiskit/quantum_info/operators/measures.py b/qiskit/quantum_info/operators/measures.py index 293c1236ed70..8b6350ab6fde 100644 --- a/qiskit/quantum_info/operators/measures.py +++ b/qiskit/quantum_info/operators/measures.py @@ -316,7 +316,7 @@ def cvx_bmat(mat_r, mat_i): iden = sparse.eye(dim_out) # Watrous uses row-vec convention for his Choi matrix while we use - # col-vec. It turns out row-vec convention is requried for CVXPY too + # col-vec. It turns out row-vec convention is required for CVXPY too # since the cvxpy.kron function must have a constant as its first argument. c_r = cvxpy.bmat([[cvxpy.kron(iden, r0_r), x_r], [x_r.T, cvxpy.kron(iden, r1_r)]]) c_i = cvxpy.bmat([[cvxpy.kron(iden, r0_i), x_i], [-x_i.T, cvxpy.kron(iden, r1_i)]]) diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 9a5e8732ae68..435120dd531b 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -185,7 +185,7 @@ def __init__(self, data, validate=True, copy=True): isinstance(data, (list, np.ndarray)) and (data_asarray := np.asarray(data, dtype=bool)).ndim == 2 ): - # This little dance is to avoid Numpy 1/2 incompatiblities between the availability + # This little dance is to avoid Numpy 1/2 incompatibilities between the availability # and meaning of the 'copy' argument in 'array' and 'asarray', when the input needs # its dtype converting. 'asarray' prefers to return 'self' if possible in both. if copy and np.may_share_memory(data, data_asarray): diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index f2e408dd9bd2..2b6e5a8774cb 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -646,7 +646,7 @@ def unique(self, return_index: bool = False, return_counts: bool = False) -> Pau index = index[sort_inds] unique = PauliList(BasePauli(self._z[index], self._x[index], self._phase[index])) - # Concatinate return tuples + # Concatenate return tuples ret = (unique,) if return_index: ret += (index,) diff --git a/qiskit/quantum_info/operators/symplectic/random.py b/qiskit/quantum_info/operators/symplectic/random.py index f9bd65ef9187..06b23ca29803 100644 --- a/qiskit/quantum_info/operators/symplectic/random.py +++ b/qiskit/quantum_info/operators/symplectic/random.py @@ -163,7 +163,7 @@ def _sample_qmallows(n, rng=None): if rng is None: rng = np.random.default_rng() - # Hadmard layer + # Hadamard layer had = np.zeros(n, dtype=bool) # Permutation layer diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py index 4ae16c32bf54..16abb67f2237 100644 --- a/qiskit/quantum_info/states/stabilizerstate.py +++ b/qiskit/quantum_info/states/stabilizerstate.py @@ -297,7 +297,7 @@ def expectation_value(self, oper: Pauli, qargs: None | list = None) -> complex: # Otherwise pauli is (-1)^a prod_j S_j^b_j for Clifford stabilizers # If pauli anti-commutes with D_j then b_j = 1. - # Multiply pauli by stabilizers with anti-commuting destabilizers + # Multiply pauli by stabilizers with anti-commuting destabilisers pauli_z = (pauli.z).copy() # Make a copy of pauli.z for p in range(num_qubits): # Check if destabilizer anti-commutes @@ -646,7 +646,7 @@ def _rowsum_nondeterministic(clifford, accum, row): @staticmethod def _rowsum_deterministic(clifford, aux_pauli, row): - """Updating an auxilary Pauli aux_pauli in the + """Updating an auxiliary Pauli aux_pauli in the deterministic rowsum calculation. The StabilizerState itself is not updated.""" @@ -680,8 +680,8 @@ def _get_probabilities( 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_prob (float): probability of the outcome + probs (dict[str, float]): holds the outcomes and probability results outcome_bitstring (str): target outcome to measure which reduces measurements, None if not targeting a specific target """ @@ -694,7 +694,7 @@ def _get_probabilities( if outcome[i] == "X": # Retrieve the qubit for the current measurement qubit = qubits[(len(qubits) - i - 1)] - # Determine if the probabilitiy is deterministic + # Determine if the probability 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 ( diff --git a/qiskit/result/counts.py b/qiskit/result/counts.py index 8168a3d21900..b34aa2373fb3 100644 --- a/qiskit/result/counts.py +++ b/qiskit/result/counts.py @@ -101,7 +101,7 @@ def __init__(self, data, time_taken=None, creg_sizes=None, memory_slots=None): else: raise TypeError( "Invalid input key type %s, must be either an int " - "key or string key with hexademical value or bit string" + "key or string key with hexadecimal value or bit string" ) header = {} self.creg_sizes = creg_sizes diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 2fc9ca5bdb29..21bea89b6575 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -412,7 +412,7 @@ def _calc_pauli_diff(cliff, cliff_target): def synth_clifford_depth_lnn(cliff): - """Synthesis of a :class:`.Clifford` into layers for linear-nearest neighbour connectivity. + """Synthesis of a :class:`.Clifford` into layers for linear-nearest neighbor connectivity. The depth of the synthesized n-qubit circuit is bounded by :math:`7n+2`, which is not optimal. It should be replaced by a better algorithm that provides depth bounded by :math:`7n-4` [3]. diff --git a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py index 8131458b2d36..ae56f9926da5 100644 --- a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py +++ b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_full.py @@ -40,7 +40,7 @@ def synth_cnotdihedral_full(elem: CNOTDihedral) -> QuantumCircuit: with optimal number of two qubit gates*, `Quantum 4(369), 2020 `_ 2. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ diff --git a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py index 83c63026a21c..bedc5c735f0b 100644 --- a/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py +++ b/qiskit/synthesis/cnotdihedral/cnotdihedral_decompose_general.py @@ -38,7 +38,7 @@ def synth_cnotdihedral_general(elem: CNOTDihedral) -> QuantumCircuit: References: 1. Andrew W. Cross, Easwar Magesan, Lev S. Bishop, John A. Smolin and Jay M. Gambetta, - *Scalable randomised benchmarking of non-Clifford gates*, + *Scalable randomized benchmarking of non-Clifford gates*, npj Quantum Inf 2, 16012 (2016). """ diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index e1db47beaeff..2e5cfeafcecd 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -109,7 +109,7 @@ def run( gate_matrix_su2 = GateSequence.from_matrix(z * gate_matrix) global_phase = np.arctan2(np.imag(z), np.real(z)) - # get the decompositon as GateSequence type + # get the decomposition as GateSequence type decomposition = self._recurse(gate_matrix_su2, recursion_degree, check_input=check_input) # simplify diff --git a/qiskit/synthesis/linear/linear_depth_lnn.py b/qiskit/synthesis/linear/linear_depth_lnn.py index 2d544f37ef94..2811b755fa42 100644 --- a/qiskit/synthesis/linear/linear_depth_lnn.py +++ b/qiskit/synthesis/linear/linear_depth_lnn.py @@ -210,7 +210,7 @@ def _north_west_to_identity(n, mat): def _optimize_cx_circ_depth_5n_line(mat): # Optimize CX circuit in depth bounded by 5n for LNN connectivity. # The algorithm [1] has two steps: - # a) transform the originl matrix to a north-west matrix (m2nw), + # a) transform the original matrix to a north-west matrix (m2nw), # b) transform the north-west matrix to identity (nw2id). # # A square n-by-n matrix A is called north-west if A[i][j]=0 for all i+j>=n diff --git a/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py index 23f24e07eab3..c0956ea3bc7c 100644 --- a/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py @@ -39,7 +39,7 @@ def _initialize_phase_schedule(mat_z): """ Given a CZ layer (represented as an n*n CZ matrix Mz) - Return a scheudle of phase gates implementing Mz in a SWAP-only netwrok + Return a schedule of phase gates implementing Mz in a SWAP-only netwrok (c.f. Alg 1, [2]) """ n = len(mat_z) @@ -173,7 +173,7 @@ def _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus): of exactly n layers of boxes, each being either a SWAP or a SWAP+. That is, each northwest diagonalization circuit can be uniquely represented by which of its n(n-1)/2 boxes are SWAP+ and which are SWAP. - Return a QuantumCircuit that computes the phase scheudle S inside CX + Return a QuantumCircuit that computes the phase schedule S inside CX """ cir = QuantumCircuit(n) @@ -217,7 +217,7 @@ def _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus): def synth_cx_cz_depth_line_my(mat_x: np.ndarray, mat_z: np.ndarray) -> QuantumCircuit: """ - Joint synthesis of a -CZ-CX- circuit for linear nearest neighbour (LNN) connectivity, + Joint synthesis of a -CZ-CX- circuit for linear nearest neighbor (LNN) connectivity, with 2-qubit depth at most 5n, based on Maslov and Yang. This method computes the CZ circuit inside the CX circuit via phase gate insertions. diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py index 6dc7db5d619b..7a195f0caf96 100644 --- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -119,7 +119,7 @@ def _create_patterns(n): def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit: - r"""Synthesis of a CZ circuit for linear nearest neighbour (LNN) connectivity, + r"""Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity, based on Maslov and Roetteler. Note that this method *reverts* the order of qubits in the circuit, diff --git a/qiskit/synthesis/stabilizer/stabilizer_decompose.py b/qiskit/synthesis/stabilizer/stabilizer_decompose.py index c43747105d04..ecdc1b3257ef 100644 --- a/qiskit/synthesis/stabilizer/stabilizer_decompose.py +++ b/qiskit/synthesis/stabilizer/stabilizer_decompose.py @@ -166,7 +166,7 @@ def _calc_pauli_diff_stabilizer(cliff, cliff_target): def synth_stabilizer_depth_lnn(stab: StabilizerState) -> QuantumCircuit: - """Synthesis of an n-qubit stabilizer state for linear-nearest neighbour connectivity, + """Synthesis of an n-qubit stabilizer state for linear-nearest neighbor connectivity, in 2-qubit depth :math:`2n+2` and two distinct CX layers, using :class:`.CXGate`\\ s and phase gates (:class:`.SGate`, :class:`.SdgGate` or :class:`.ZGate`). diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 26a5b52521b2..3269797827e2 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -782,7 +782,7 @@ def __call__(self, mat): # This weird duplicated lazy structure is for backwards compatibility; Qiskit has historically # always made ``two_qubit_cnot_decompose`` available publicly immediately on import, but it's quite -# expensive to construct, and we want to defer the obejct's creation until it's actually used. We +# expensive to construct, and we want to defer the object's creation until it's actually used. We # only need to pass through the public methods that take `self` as a parameter. Using `__getattr__` # doesn't work because it is only called if the normal resolution methods fail. Using # `__getattribute__` is too messy for a simple one-off use object. diff --git a/qiskit/synthesis/unitary/aqc/cnot_structures.py b/qiskit/synthesis/unitary/aqc/cnot_structures.py index 8659f0c2c343..978b1fc84e66 100644 --- a/qiskit/synthesis/unitary/aqc/cnot_structures.py +++ b/qiskit/synthesis/unitary/aqc/cnot_structures.py @@ -133,7 +133,7 @@ def _get_connectivity(num_qubits: int, connectivity: str) -> dict: links = {i: list(range(num_qubits)) for i in range(num_qubits)} elif connectivity == "line": - # Every qubit is connected to its immediate neighbours only. + # Every qubit is connected to its immediate neighbors only. links = {i: [i - 1, i, i + 1] for i in range(1, num_qubits - 1)} # first qubit diff --git a/qiskit/synthesis/unitary/qsd.py b/qiskit/synthesis/unitary/qsd.py index 80a8afc1311b..525daa3caf15 100644 --- a/qiskit/synthesis/unitary/qsd.py +++ b/qiskit/synthesis/unitary/qsd.py @@ -269,7 +269,7 @@ def _apply_a2(circ): # rolling over diagonals ind2 = None # lint for ind1, ind2 in zip(ind2q[0:-1:], ind2q[1::]): - # get neigboring 2q gates separated by controls + # get neighboring 2q gates separated by controls instr1 = ccirc.data[ind1] mat1 = Operator(instr1.operation).data instr2 = ccirc.data[ind2] diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 936613744b84..f2e752dd94f5 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -466,7 +466,7 @@ def discover_vertex(self, v, score): score, ) self._basis_transforms.append((gate.name, gate.num_qubits, rule.params, rule.circuit)) - # we can stop the search if we have found all gates in the original ciruit. + # we can stop the search if we have found all gates in the original circuit. if not self._source_gates_remain: # if we start from source gates and apply `basis_transforms` in reverse order, we'll end # up with gates in the target basis. Note though that `basis_transforms` may include @@ -548,7 +548,7 @@ def _basis_search(equiv_lib, source_basis, target_basis): if not source_basis: return [] - # This is only neccessary since gates in target basis are currently reported by + # This is only necessary since gates in target basis are currently reported by # their names and we need to have in addition the number of qubits they act on. target_basis_keys = [key for key in equiv_lib.keys() if key.name in target_basis] diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 31609b878683..2fb9a1890bd5 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -144,7 +144,7 @@ def __init__( with the ``routing_pass`` argument and an error will be raised if both are used. layout_trials (int): The number of random seed trials to run - layout with. When > 1 the trial that resuls in the output with + layout with. When > 1 the trial that results in the output with the fewest swap gates will be selected. If this is not specified (and ``routing_pass`` is not set) then the number of local physical CPUs will be used as the default value. This option is @@ -420,7 +420,7 @@ def _inner_run(self, dag, coupling_map, starting_layouts=None): ) def _ancilla_allocation_no_pass_manager(self, dag): - """Run the ancilla-allocation and -enlargment passes on the DAG chained onto our + """Run the ancilla-allocation and -enlargement passes on the DAG chained onto our ``property_set``, skipping the DAG-to-circuit conversion cost of using a ``PassManager``.""" ancilla_pass = FullAncillaAllocation(self.coupling_map) ancilla_pass.property_set = self.property_set diff --git a/qiskit/transpiler/passes/layout/vf2_layout.py b/qiskit/transpiler/passes/layout/vf2_layout.py index 626f8f2b0fa3..2e799ffa4d95 100644 --- a/qiskit/transpiler/passes/layout/vf2_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_layout.py @@ -195,7 +195,7 @@ def mapping_to_layout(layout_mapping): if len(cm_graph) == len(im_graph): chosen_layout = mapping_to_layout(layout_mapping) break - # If there is no error map avilable we can just skip the scoring stage as there + # If there is no error map available we can just skip the scoring stage as there # is nothing to score with, so any match is the best we can find. if self.avg_error_map is None: chosen_layout = mapping_to_layout(layout_mapping) diff --git a/qiskit/transpiler/passes/optimization/commutative_cancellation.py b/qiskit/transpiler/passes/optimization/commutative_cancellation.py index 68d40f3650a2..4c6c487a0ea3 100644 --- a/qiskit/transpiler/passes/optimization/commutative_cancellation.py +++ b/qiskit/transpiler/passes/optimization/commutative_cancellation.py @@ -75,7 +75,7 @@ def run(self, dag): var_z_gate = None z_var_gates = [gate for gate in dag.count_ops().keys() if gate in self._var_z_map] if z_var_gates: - # priortize z gates in circuit + # prioritize z gates in circuit var_z_gate = self._var_z_map[next(iter(z_var_gates))] else: z_var_gates = [gate for gate in self.basis if gate in self._var_z_map] diff --git a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py index a4b11a33de2d..d194d1cbbddf 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/backward_match.py +++ b/qiskit/transpiler/passes/optimization/template_matching/backward_match.py @@ -622,7 +622,7 @@ def run_backward_match(self): ) self.matching_list.append_scenario(new_matching_scenario) - # Third option: if blocking the succesors breaks a match, we consider + # Third option: if blocking the successors breaks a match, we consider # also the possibility to block all predecessors (push the gate to the left). if broken_matches and all(global_broken): diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 06c5186d284c..44689894176a 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -507,7 +507,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): to_native_symbolic = lambda x: x circuit_params, template_params = [], [] - # Set of all parameter names that are present in the circuits to be optimised. + # Set of all parameter names that are present in the circuits to be optimized. circuit_params_set = set() template_dag_dep = copy.deepcopy(self.template_dag_dep) diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py index b79a298ad595..3679e8bfb8e3 100644 --- a/qiskit/transpiler/passes/routing/star_prerouting.py +++ b/qiskit/transpiler/passes/routing/star_prerouting.py @@ -260,7 +260,7 @@ def run(self, dag): # star block by a linear sequence of gates new_dag, qubit_mapping = self.star_preroute(dag, star_blocks, processing_order) - # Fix output permuation -- copied from ElidePermutations + # 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: diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index 513144937ab5..8478f241c267 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -44,7 +44,7 @@ multiple of this value. Violation of this constraint may result in the backend execution failure. - In most of the senarios, the scheduled start time of ``DAGOpNode`` corresponds to the + In most of the scenarios, the scheduled start time of ``DAGOpNode`` corresponds to the start time of the underlying pulse instruction composing the node operation. However, this assumption can be intentionally broken by defining a pulse gate, i.e. calibration, with the schedule involving pre-buffer, i.e. some random pulse delay @@ -62,7 +62,7 @@ This value is reported by ``timing_constraints["granularity"]`` in the backend configuration in units of dt. This is the constraint for a single pulse :class:`Play` instruction that may constitute your pulse gate. - The length of waveform samples should be multipel of this constraint value. + The length of waveform samples should be multiple of this constraint value. Violation of this constraint may result in failue in backend execution. Minimum pulse length constraint diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 4085844a4709..e9076c5c637b 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -68,7 +68,7 @@ class BaseSchedulerTransform(TransformationPass): However, such optimization should be done by another pass, otherwise scheduling may break topological ordering of the original circuit. - Realistic control flow scheduling respecting for microarcitecture + Realistic control flow scheduling respecting for microarchitecture In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) followed by resonator ring-down (depopulation). This microwave signal is recorded diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 12f4bc515b29..7be0e838ebff 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -130,7 +130,7 @@ def __init__( will be used [d/2, d, d, ..., d, d, d/2]. skip_reset_qubits (bool): if True, does not insert DD on idle periods that immediately follow initialized/reset qubits (as - qubits in the ground state are less susceptile to decoherence). + qubits in the ground state are less susceptible to decoherence). target (Target): The :class:`~.Target` representing the target backend, if both ``durations`` and this are specified then this argument will take precedence and ``durations`` will be ignored. diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index a90f0c339ced..4ce17e7bc261 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -202,7 +202,7 @@ def _apply_scheduled_op( ): """Add new operation to DAG with scheduled information. - This is identical to apply_operation_back + updating the node_start_time propety. + This is identical to apply_operation_back + updating the node_start_time property. Args: dag: DAG circuit on which the sequence is applied. diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 806e001f2bda..45333de009bd 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -128,7 +128,7 @@ def __init__( will be used [d/2, d, d, ..., d, d, d/2]. skip_reset_qubits: If True, does not insert DD on idle periods that immediately follow initialized/reset qubits - (as qubits in the ground state are less susceptile to decoherence). + (as qubits in the ground state are less susceptible to decoherence). pulse_alignment: The hardware constraints for gate timing allocation. This is usually provided from ``backend.configuration().timing_constraints``. If provided, the delay length, i.e. ``spacing``, is implicitly adjusted to @@ -311,7 +311,7 @@ def _pad( # slack = 992 dt - 4 x 160 dt = 352 dt # # unconstraind sequence: 44dt-X1-88dt-Y2-88dt-X3-88dt-Y4-44dt - # constraind sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt + # constrained sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt # # Now we evenly split extra slack into start and end of the sequence. # The distributed slack should be multiple of 16. diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a30411d16a93..7db48d6d1395 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -414,7 +414,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if self.method == "default": # If the method is the default, we only need to evaluate one set of keyword arguments. # To simplify later logic, and avoid cases where static analysis might complain that we - # haven't initialised the "default" handler, we rebind the names so they point to the + # haven't initialized the "default" handler, we rebind the names so they point to the # same object as the chosen method. default_method = plugin_method default_kwargs = plugin_kwargs diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 98b471f6f7f0..79493ae8ad25 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -67,7 +67,7 @@ class GateDirection(TransformationPass): └──────┘ └───┘└──────┘└───┘ This pass assumes that the positions of the qubits in the :attr:`.DAGCircuit.qubits` attribute - are the physical qubit indicies. For example if ``dag.qubits[0]`` is qubit 0 in the + are the physical qubit indices. For example if ``dag.qubits[0]`` is qubit 0 in the :class:`.CouplingMap` or :class:`.Target`. """ diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index f590a1510bda..c905d6142144 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -338,7 +338,7 @@ def __init__(self, stages: Iterable[str] | None = None, **kwargs) -> None: "scheduling", ] self._validate_stages(stages) - # Set through parent class since `__setattr__` requieres `expanded_stages` to be defined + # Set through parent class since `__setattr__` requires `expanded_stages` to be defined super().__setattr__("_stages", tuple(stages)) super().__setattr__("_expanded_stages", tuple(self._generate_expanded_stages())) super().__init__() diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py index 1e58b1ad2b91..7dae35d1349e 100644 --- a/qiskit/utils/classtools.py +++ b/qiskit/utils/classtools.py @@ -31,7 +31,7 @@ class _lift_to_method: # pylint: disable=invalid-name returned unchanged if so, otherwise it is turned into the default implementation for functions, which makes them bindable to instances. - Python-space functions and lambdas already have this behaviour, but builtins like ``print`` + Python-space functions and lambdas already have this behavior, but builtins like ``print`` don't; using this class allows us to do:: wrap_method(MyClass, "maybe_mutates_arguments", before=print, after=print) @@ -49,7 +49,7 @@ def __new__(cls, method): def __init__(self, method): if method is self: - # Prevent double-initialisation if we are passed an instance of this object to lift. + # Prevent double-initialization if we are passed an instance of this object to lift. return self._method = method @@ -118,7 +118,7 @@ def out(*args, **kwargs): def wrap_method(cls: Type, name: str, *, before: Callable = None, after: Callable = None): - """Wrap the functionality the instance- or class method ``cls.name`` with additional behaviour + """Wrap the functionality the instance- or class method ``cls.name`` with additional behavior ``before`` and ``after``. This mutates ``cls``, replacing the attribute ``name`` with the new functionality. This is diff --git a/qiskit/utils/lazy_tester.py b/qiskit/utils/lazy_tester.py index f2c4c3803150..58c5931fd5ea 100644 --- a/qiskit/utils/lazy_tester.py +++ b/qiskit/utils/lazy_tester.py @@ -174,7 +174,7 @@ def require_in_instance(self, feature_or_class: str) -> Callable[[Type], Type]: def require_in_instance(self, feature_or_class): """A class decorator that requires the dependency is available when the class is - initialised. This decorator can be used even if the class does not define an ``__init__`` + initialized. This decorator can be used even if the class does not define an ``__init__`` method. Args: @@ -186,7 +186,7 @@ def require_in_instance(self, feature_or_class): Returns: Callable: a class decorator that ensures that the wrapped feature is present if the - class is initialised. + class is initialized. """ if isinstance(feature_or_class, str): feature = feature_or_class diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index f2b1e56e1120..f2e6c860faaa 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -79,7 +79,7 @@ * - .. py:data:: HAS_IPYTHON - If `the IPython kernel `__ is available, certain additional - visualisations and line magics are made available. + visualizations and line magics are made available. * - .. py:data:: HAS_IPYWIDGETS - Monitoring widgets for jobs running on external backends can be provided if `ipywidgets @@ -94,7 +94,7 @@ interactivity features. * - .. py:data:: HAS_MATPLOTLIB - - Qiskit provides several visualisation tools in the :mod:`.visualization` module. + - Qiskit provides several visualization tools in the :mod:`.visualization` module. Almost all of these are built using `Matplotlib `__, which must be installed in order to use them. @@ -116,7 +116,7 @@ :class:`.DAGCircuit` in certain modes. * - .. py:data:: HAS_PYDOT - - For some graph visualisations, Qiskit uses `pydot `__ as an + - For some graph visualizations, Qiskit uses `pydot `__ as an interface to GraphViz (see :data:`HAS_GRAPHVIZ`). * - .. py:data:: HAS_PYGMENTS @@ -134,7 +134,7 @@ `__. * - .. py:data:: HAS_SEABORN - - Qiskit provides several visualisation tools in the :mod:`.visualization` module. Some + - Qiskit provides several visualization tools in the :mod:`.visualization` module. Some of these are built using `Seaborn `__, which must be installed in order to use them. @@ -179,16 +179,16 @@ :widths: 25 75 * - .. py:data:: HAS_GRAPHVIZ - - For some graph visualisations, Qiskit uses the `GraphViz `__ - visualisation tool via its ``pydot`` interface (see :data:`HAS_PYDOT`). + - For some graph visualizations, Qiskit uses the `GraphViz `__ + visualization tool via its ``pydot`` interface (see :data:`HAS_PYDOT`). * - .. py:data:: HAS_PDFLATEX - - Visualisation tools that use LaTeX in their output, such as the circuit drawers, require + - Visualization tools that use LaTeX in their output, such as the circuit drawers, require ``pdflatex`` to be available. You will generally need to ensure that you have a working LaTeX installation available, and the ``qcircuit.tex`` package. * - .. py:data:: HAS_PDFTOCAIRO - - Visualisation tools that convert LaTeX-generated files into rasterised images use the + - Visualization tools that convert LaTeX-generated files into rasterized images use the ``pdftocairo`` tool. This is part of the `Poppler suite of PDF tools `__. diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index c14bb3d46c29..ca29794b9626 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -112,7 +112,7 @@ def get_gate_ctrl_text(op, drawer, style=None, calibrations=None): gate_text = gate_text.replace("-", "\\mbox{-}") ctrl_text = f"$\\mathrm{{{ctrl_text}}}$" - # Only captitalize internally-created gate or instruction names + # Only capitalize internally-created gate or instruction names elif ( (gate_text == op.name and op_type not in (Gate, Instruction)) or (gate_text == base_name and base_type not in (Gate, Instruction)) diff --git a/qiskit/visualization/pulse_v2/events.py b/qiskit/visualization/pulse_v2/events.py index 4bb59cd86260..74da24b1db25 100644 --- a/qiskit/visualization/pulse_v2/events.py +++ b/qiskit/visualization/pulse_v2/events.py @@ -196,7 +196,7 @@ def get_waveforms(self) -> Iterator[PulseInstruction]: def get_frame_changes(self) -> Iterator[PulseInstruction]: """Return frame change type instructions with total frame change amount.""" - # TODO parse parametrised FCs correctly + # TODO parse parametrized FCs correctly sorted_frame_changes = sorted(self._frames.items(), key=lambda x: x[0]) diff --git a/releasenotes/config.yaml b/releasenotes/config.yaml index bea33ef99a1e..0c621662ca84 100644 --- a/releasenotes/config.yaml +++ b/releasenotes/config.yaml @@ -87,7 +87,7 @@ template: | New features related to the qiskit.qpy module. features_quantum_info: - | - New features releated to the qiskit.quantum_info module. + New features related to the qiskit.quantum_info module. features_synthesis: - | New features related to the qiskit.synthesis module. @@ -178,7 +178,7 @@ template: | Deprecations related to the qiskit.qpy module. deprecations_quantum_info: - | - Deprecations releated to the qiskit.quantum_info module. + Deprecations related to the qiskit.quantum_info module. deprecations_synthesis: - | Deprecations related to the qiskit.synthesis module. diff --git a/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml b/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml index 9dd4a241a09d..0e778de9665a 100644 --- a/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml +++ b/releasenotes/notes/0.12/operator-dot-fd90e7e5ad99ff9b.yaml @@ -22,4 +22,4 @@ upgrade: from the right hand side of the operation if the left does not have ``__mul__`` defined) implements scalar multiplication (i.e. :meth:`qiskit.quantum_info.Operator.multiply`). Previously both methods - implemented scalar multiplciation. + implemented scalar multiplication. diff --git a/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml b/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml index 30561a9aadc8..71fe512aa778 100644 --- a/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml +++ b/releasenotes/notes/0.13/0.13.0-release-a92553cf72c203aa.yaml @@ -8,7 +8,7 @@ prelude: | structure behind all operations to be based on `retworkx `_ for greatly improved performance. Circuit transpilation speed in the 0.13.0 release should - be significanlty faster than in previous releases. + be significantly faster than in previous releases. There has been a significant simplification to the style in which Pulse instructions are built. Now, ``Command`` s are deprecated and a unified diff --git a/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml b/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml index 59956851a81f..96fb649b07dd 100644 --- a/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml +++ b/releasenotes/notes/0.13/add-base-job-status-methods-3ab9646c5f5470a6.yaml @@ -8,4 +8,4 @@ features: * :meth:`~qiskit.providers.BaseJob.cancelled` * :meth:`~qiskit.providers.BaseJob.in_final_state` - These methods are used to check wheter a job is in a given job status. + These methods are used to check whether a job is in a given job status. diff --git a/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml b/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml index d8df9b53fdc0..24b00a262e0f 100644 --- a/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml +++ b/releasenotes/notes/0.13/default-schedule-name-51ba198cf08978cd.yaml @@ -2,6 +2,6 @@ fixes: - | Fixes a case in :meth:`qiskit.result.Result.get_counts`, where the results - for an expirement could not be referenced if the experiment was initialized + for an experiment could not be referenced if the experiment was initialized as a Schedule without a name. Fixes `#2753 `_ diff --git a/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml b/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml index 8384df8d205f..ba4f07afd895 100644 --- a/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml +++ b/releasenotes/notes/0.13/qinfo-operators-0193871295190bad.yaml @@ -11,7 +11,7 @@ features: the number of two-qubit gates. - | Adds :class:`qiskit.quantum_info.SparsePauliOp` operator class. This is an - efficient representaiton of an N-qubit matrix that is sparse in the Pauli + efficient representation of an N-qubit matrix that is sparse in the Pauli basis and uses a :class:`qiskit.quantum_info.PauliTable` and vector of complex coefficients for its data structure. @@ -23,7 +23,7 @@ features: Numpy arrays or :class:`~qiskit.quantum_info.Operator` objects can be converted to a :class:`~qiskit.quantum_info.SparsePauliOp` using the `:class:`~qiskit.quantum_info.SparsePauliOp.from_operator` method. - :class:`~qiskit.quantum_info.SparsePauliOp` can be convered to a sparse + :class:`~qiskit.quantum_info.SparsePauliOp` can be converted to a sparse csr_matrix or dense Numpy array using the :class:`~qiskit.quantum_info.SparsePauliOp.to_matrix` method, or to an :class:`~qiskit.quantum_info.Operator` object using the @@ -54,7 +54,7 @@ features: :meth:`~qiskit.quantum_info.PauliTable.tensor`) between each element of the first table, with each element of the second table. - * Addition of two tables acts as list concatination of the terms in each + * Addition of two tables acts as list concatenation of the terms in each table (``+``). * Pauli tables can be sorted by lexicographic (tensor product) order or @@ -148,7 +148,7 @@ upgrade: n_qubits = 10 ham = ScalarOp(2 ** n_qubits, coeff=0) - # Add 2-body nearest neighbour terms + # Add 2-body nearest neighbor terms for j in range(n_qubits - 1): ham = ham + ZZ([j, j+1]) - | diff --git a/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml b/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml index 9a8a84530832..df465a05c439 100644 --- a/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml +++ b/releasenotes/notes/0.13/qinfo-states-7f67e2432cf0c12c.yaml @@ -175,7 +175,7 @@ deprecations: The ``add``, ``subtract``, and ``multiply`` methods of the :class:`qiskit.quantum_info.Statevector` and :class:`qiskit.quantum_info.DensityMatrix` classes are deprecated and will - be removed in a future release. Instead you shoulde use ``+``, ``-``, ``*`` + be removed in a future release. Instead you should use ``+``, ``-``, ``*`` binary operators instead. - | Deprecates :meth:`qiskit.quantum_info.Statevector.to_counts`, diff --git a/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml b/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml index 12c69ef47c51..21e14b6ef548 100644 --- a/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml +++ b/releasenotes/notes/0.13/quibit-transition-visualization-a62d0d119569fa05.yaml @@ -6,7 +6,7 @@ features: single qubit gate transitions has been added. It takes in a single qubit circuit and returns an animation of qubit state transitions on a Bloch sphere. To use this function you must have installed - the dependencies for and configured globally a matplotlib animtion + the dependencies for and configured globally a matplotlib animation writer. You can refer to the `matplotlib documentation `_ for more details on this. However, in the default case simply ensuring diff --git a/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml b/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml index e30386b5dfd1..3fbb8558afce 100644 --- a/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml +++ b/releasenotes/notes/0.15/parameter-conjugate-a16fd7ae0dc18ede.yaml @@ -5,4 +5,4 @@ features: been added to the :class:`~qiskit.circuit.ParameterExpression` class. This enables calling ``numpy.conj()`` without raising an error. Since a :class:`~qiskit.circuit.ParameterExpression` object is real, it will - return itself. This behaviour is analogous to Python floats/ints. + return itself. This behavior is analogous to Python floats/ints. diff --git a/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml b/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml index a9ffc4d508df..bc60745c71d3 100644 --- a/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml +++ b/releasenotes/notes/0.16/delay-in-circuit-33f0d81783ac12ea.yaml @@ -43,7 +43,7 @@ features: of scheduled circuits. - | - A new fuction :func:`qiskit.compiler.sequence` has been also added so that + A new function :func:`qiskit.compiler.sequence` has been also added so that we can convert a scheduled circuit into a :class:`~qiskit.pulse.Schedule` to make it executable on a pulse-enabled backend. diff --git a/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml b/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml index 9b441e252c17..158550d9a09a 100644 --- a/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml +++ b/releasenotes/notes/0.16/fix-bug-in-controlled-unitary-when-setting-ctrl_state-2f9af3b9f0f7903f.yaml @@ -6,4 +6,4 @@ fixes: in the creation of the matrix for the controlled unitary and again when calling the :meth:`~qiskit.circuit.ControlledGate.definition` method of the :class:`qiskit.circuit.ControlledGate` class. This would give the - appearence that setting ``ctrl_state`` had no effect. + appearance that setting ``ctrl_state`` had no effect. diff --git a/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml b/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml index a0b69d5333c3..42f696351557 100644 --- a/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml +++ b/releasenotes/notes/0.16/remove-dagnode-dict-32fa35479c0a8331.yaml @@ -3,7 +3,7 @@ upgrade: - | The previously deprecated support for passing in a dictionary as the first positional argument to :class:`~qiskit.dagcircuit.DAGNode` constructor - has been removed. Using a dictonary for the first positional argument + has been removed. Using a dictionary for the first positional argument was deprecated in the 0.13.0 release. To create a :class:`~qiskit.dagcircuit.DAGNode` object now you should directly pass the attributes as kwargs on the constructor. diff --git a/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml b/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml index 0cde632a36b6..fc317417747a 100644 --- a/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml +++ b/releasenotes/notes/0.17/add-schedule-block-c37527f3205b7b62.yaml @@ -49,7 +49,7 @@ deprecations: constructing parameterized pulse programs. - | The :attr:`~qiskit.pulse.channels.Channel.parameters` attribute for - the following clasess: + the following classes: * :py:class:`~qiskit.pulse.channels.Channel` * :py:class:`~qiskit.pulse.instructions.Instruction`. diff --git a/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml b/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml index 85b6a4cd37b7..a688665aea53 100644 --- a/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml +++ b/releasenotes/notes/0.17/basicaer-new-provider-ea7cf756df231c2b.yaml @@ -34,7 +34,7 @@ upgrade: until the simulation finishes executing. If you want to restore the previous async behavior you'll need to wrap the :meth:`~qiskit.providers.basicaer.QasmSimulatorPy.run` with something that - will run in a seperate thread or process like ``futures.ThreadPoolExecutor`` + will run in a separate thread or process like ``futures.ThreadPoolExecutor`` or ``futures.ProcessPoolExecutor``. - | The ``allow_sample_measuring`` option for the diff --git a/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml b/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml index dd9b8052e5a9..d627ecfe47c8 100644 --- a/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml +++ b/releasenotes/notes/0.17/deprecate-schemas-424c29fbd35c90de.yaml @@ -11,7 +11,7 @@ deprecations: deprecation warning). The schema files have been moved to the `Qiskit/ibmq-schemas `__ repository and those should be treated as the canonical versions of the - API schemas. Moving forward only those schemas will recieve updates and + API schemas. Moving forward only those schemas will receive updates and will be used as the source of truth for the schemas. If you were relying on the schemas bundled in qiskit-terra you should update to use that repository instead. diff --git a/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml b/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml index 5ddd2771072e..42aeec0b513b 100644 --- a/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml +++ b/releasenotes/notes/0.17/ecr-gate-45cfda1b84ac792c.yaml @@ -25,7 +25,7 @@ features: - | Two new transpiler passess, :class:`~qiskit.transpiler.GateDirection` and class:`qiskit.transpiler.CheckGateDirection`, were added to the - :mod:`qiskit.transpiler.passes` module. These new passes are inteded to + :mod:`qiskit.transpiler.passes` module. These new passes are intended to be more general replacements for :class:`~qiskit.transpiler.passes.CXDirection` and :class:`~qiskit.transpiler.passes.CheckCXDirection` (which are both now diff --git a/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml b/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml index 1dcd19829554..4adadd4a7c8d 100644 --- a/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml +++ b/releasenotes/notes/0.17/fix-nlocal-circular-entanglement-0acf0195138b6aa2.yaml @@ -5,6 +5,6 @@ fixes: :class:`qiskit.circuit.library.NLocal` circuit class for the edge case where the circuit has the same size as the entanglement block (e.g. a two-qubit circuit and CZ entanglement gates). In this case there should only be one entanglement - gate, but there was accidentially added a second one in the inverse direction as the + gate, but there was accidentally added a second one in the inverse direction as the first. Fixed `Qiskit/qiskit-aqua#1452 `__ diff --git a/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml b/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml index e306e5a1558e..824a95563080 100644 --- a/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml +++ b/releasenotes/notes/0.17/idle-time-visualization-b5404ad875cbdae4.yaml @@ -2,7 +2,7 @@ fixes: - | Fixed an issue with the :func:`qiskit.visualization.timeline_drawer` - function where classical bits were inproperly handled. + function where classical bits were improperly handled. Fixed `#5361 `__ - | Fixed an issue in the :func:`qiskit.visualization.circuit_drawer` function diff --git a/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml b/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml index e5553896c76d..d7a8d21f974d 100644 --- a/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml +++ b/releasenotes/notes/0.17/issue-5751-1b6249f6263c9c30.yaml @@ -17,5 +17,5 @@ features: the :class:`~qiskit.transpiler.passes.TemplateOptimization` pass with the :py:class:`qiskit.transpiler.passes.RZXCalibrationBuilder` pass to automatically find and replace gate sequences, such as - ``CNOT - P(theta) - CNOT``, with more efficent circuits based on + ``CNOT - P(theta) - CNOT``, with more efficient circuits based on :class:`qiskit.circuit.library.RZXGate` with a calibration. diff --git a/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml b/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml index 0bc3fbf78fff..f30cea6b27f8 100644 --- a/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml +++ b/releasenotes/notes/0.17/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml @@ -13,7 +13,7 @@ upgrade: this change. - | The ``qiskit.execute`` module has been renamed to - :mod:`qiskit.execute_function`. This was necessary to avoid a potentical + :mod:`qiskit.execute_function`. This was necessary to avoid a potential name conflict between the :func:`~qiskit.execute_function.execute` function which is re-exported as ``qiskit.execute``. ``qiskit.execute`` the function in some situations could conflict with ``qiskit.execute`` the module which @@ -30,7 +30,7 @@ upgrade: been renamed to ``qiskit.compiler.transpiler``, ``qiskit.compiler.assembler``, ``qiskit.compiler.scheduler``, and ``qiskit.compiler.sequence`` respectively. This was necessary to avoid a - potentical name conflict between the modules and the re-exported function + potential name conflict between the modules and the re-exported function paths :func:`qiskit.compiler.transpile`, :func:`qiskit.compiler.assemble`, :func:`qiskit.compiler.schedule`, and :func:`qiskit.compiler.sequence`. In some situations this name conflict between the module path and diff --git a/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml b/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml index ef5a733d2a02..a5a75974ed11 100644 --- a/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml +++ b/releasenotes/notes/0.17/replace-pulse-drawer-f9f667c8f71e1e02.yaml @@ -15,7 +15,7 @@ features: * Specifying ``axis`` objects for plotting to allow further extension of generated plots, i.e., for publication manipulations. - New stylesheets can take callback functions that dynamically modify the apperance of + New stylesheets can take callback functions that dynamically modify the appearance of the output image, for example, reassembling a collection of channels, showing details of instructions, updating appearance of pulse envelopes, etc... You can create custom callback functions and feed them into a stylesheet instance to diff --git a/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml b/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml index bd614d766f50..1c70b64a91f4 100644 --- a/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml +++ b/releasenotes/notes/0.18/add-pauli-list-5644d695f91de808.yaml @@ -4,7 +4,7 @@ features: A new class, :class:`~qiskit.quantum_info.PauliList`, has been added to the :mod:`qiskit.quantum_info` module. This class is used to efficiently represent a list of :class:`~qiskit.quantum_info.Pauli` - operators. This new class inherets from the same parent class as the + operators. This new class inherits from the same parent class as the existing :class:`~qiskit.quantum_info.PauliTable` (and therefore can be mostly used interchangeably), however it differs from the :class:`~qiskit.quantum_info.PauliTable` diff --git a/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml b/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml index b4b2bbbd8b37..c37fe6bbcdc6 100644 --- a/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml +++ b/releasenotes/notes/0.19/fix-infinite-job-submissions-d6f6a583535ca798.yaml @@ -5,7 +5,7 @@ features: to limit the number of times a job will attempt to be executed on a backend. Previously the submission and fetching of results would be attempted infinitely, even if the job was cancelled or errored on the backend. The - default is now 50, and the previous behaviour can be achieved by setting + default is now 50, and the previous behavior can be achieved by setting ``max_job_tries=-1``. Fixes `#6872 `__ and `#6821 `__. diff --git a/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml b/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml index 2da1ce1a40d7..fbde9d704a17 100644 --- a/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml +++ b/releasenotes/notes/0.19/gates-in-basis-pass-337f6637e61919db.yaml @@ -13,7 +13,7 @@ features: from qiskit.circuit import QuantumCircuit from qiskit.transpiler.passes import GatesInBasis - # Instatiate Pass + # Instantiate Pass basis_gates = ["cx", "h"] basis_check_pass = GatesInBasis(basis_gates) # Build circuit diff --git a/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml b/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml index d5737bba8e57..bdfca2b5c577 100644 --- a/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml +++ b/releasenotes/notes/0.19/measure_all-add_bits-8525317935197b90.yaml @@ -2,7 +2,7 @@ features: - | Added a new parameter, ``add_bits``, to :meth:`.QuantumCircuit.measure_all`. - By default it is set to ``True`` to maintain the previous behaviour of adding a new :obj:`.ClassicalRegister` of the same size as the number of qubits to store the measurements. + By default it is set to ``True`` to maintain the previous behavior of adding a new :obj:`.ClassicalRegister` of the same size as the number of qubits to store the measurements. If set to ``False``, the measurements will be stored in the already existing classical bits. For example, if you created a circuit with existing classical bits like:: diff --git a/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml b/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml index 16e636ab7c94..c553ee8f0ca6 100644 --- a/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml +++ b/releasenotes/notes/0.19/mpl-bump-33a1240266e66508.yaml @@ -10,5 +10,5 @@ upgrade: deprecated the use of APIs around 3D visualizations that were compatible with older releases and second installing older versions of Matplotlib was becoming increasingly difficult as matplotlib's upstream dependencies - have caused incompatiblities that made testing moving forward more + have caused incompatibilities that made testing moving forward more difficult. diff --git a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml index 69d60ee1545f..26058e03f3dc 100644 --- a/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml +++ b/releasenotes/notes/0.19/readout-mitigation-classes-2ef175e232d791ae.yaml @@ -25,7 +25,7 @@ features: - | Added the :class:`~qiskit.result.LocalReadoutMitigator` class for performing measurement readout error mitigation of local measurement - errors. Local measuerment errors are those that are described by a + errors. Local measurement errors are those that are described by a tensor-product of single-qubit measurement errors. This class can be initialized with a list of :math:`N` single-qubit of @@ -40,7 +40,7 @@ features: performing measurement readout error mitigation of correlated measurement errors. This class can be initialized with a single :math:`2^N \times 2^N` measurement error assignment matrix that descirbes the error probabilities. - Mitigation is implemented via inversion of assigment matrix which has + Mitigation is implemented via inversion of assignment matrix which has mitigation complexity of :math:`O(4^N)` of :class:`~qiskit.result.QuasiDistribution` and expectation values. - | diff --git a/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml b/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml index 15ee5cc07ce6..1ec1774e680a 100644 --- a/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml +++ b/releasenotes/notes/0.19/remove-manual-warning-filters-028646b73bb86860.yaml @@ -2,12 +2,12 @@ upgrade: - | An internal filter override that caused all Qiskit deprecation warnings to - be displayed has been removed. This means that the behaviour will now - revert to the standard Python behaviour for deprecations; you should only + be displayed has been removed. This means that the behavior will now + revert to the standard Python behavior for deprecations; you should only see a ``DeprecationWarning`` if it was triggered by code in the main script file, interpreter session or Jupyter notebook. The user will no longer be blamed with a warning if internal Qiskit functions call deprecated - behaviour. If you write libraries, you should occasionally run with the + behavior. If you write libraries, you should occasionally run with the default warning filters disabled, or have tests which always run with them disabled. See the `Python documentation on warnings`_, and in particular the `section on testing for deprecations`_ for more information on how to do this. @@ -16,7 +16,7 @@ upgrade: .. _section on testing for deprecations: https://docs.python.org/3/library/warnings.html#updating-code-for-new-versions-of-dependencies - | Certain warnings used to be only issued once, even if triggered from - multiple places. This behaviour has been removed, so it is possible that if + multiple places. This behavior has been removed, so it is possible that if you call deprecated functions, you may see more warnings than you did before. You should change any deprecated function calls to the suggested versions, because the deprecated forms will be removed in future Qiskit diff --git a/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml b/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml index 4ce25f211465..a9623206ecdb 100644 --- a/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml +++ b/releasenotes/notes/0.19/sparse-pauli-internal-8226b4f57a61b982.yaml @@ -12,5 +12,5 @@ upgrade: The return type of :func:`~qiskit.quantum_info.pauli_basis` will change from :class:`~qiskit.quantum_info.PauliTable` to :class:`~qiskit.quantum_info.PauliList` in a future release of Qiskit Terra. - To immediately swap to the new behaviour, pass the keyword argument + To immediately swap to the new behavior, pass the keyword argument ``pauli_list=True``. diff --git a/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml b/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml index 07c0ce9178df..74d66c7558cf 100644 --- a/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml +++ b/releasenotes/notes/0.19/vf2layout-4cea88087c355769.yaml @@ -7,7 +7,7 @@ features: `__ to find a perfect layout (a layout which would not require additional routing) if one exists. The functionality exposed by this new pass is very - similar to exisiting :class:`~qiskit.transpiler.passes.CSPLayout` but + similar to existing :class:`~qiskit.transpiler.passes.CSPLayout` but :class:`~qiskit.transpiler.passes.VF2Layout` is significantly faster. .. _VF2 algorithm: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.101.5342&rep=rep1&type=pdf diff --git a/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml b/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml index e6dc2829ca02..db9b15d86252 100644 --- a/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml +++ b/releasenotes/notes/0.20/expose-tolerances-z2symmetries-9c444a7b1237252e.yaml @@ -35,7 +35,7 @@ features: SparsePauliOp(['X', 'Y'], coeffs=[1.+0.j, 0.+1.j]) - Note that the chop method does not accumulate the coefficents of the same Paulis, e.g. + Note that the chop method does not accumulate the coefficients of the same Paulis, e.g. .. code-block:: diff --git a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml index af5f158ecb17..5f79ace148ed 100644 --- a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml +++ b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml @@ -12,7 +12,7 @@ features: Previously, the pass chain would have been implemented as ``scheduling -> alignment`` which were both transform passes thus there were multiple :class:`~.DAGCircuit` - instances recreated during each pass. In addition, scheduling occured in each pass + instances recreated during each pass. In addition, scheduling occurred in each pass to obtain instruction start time. Now the required pass chain becomes ``scheduling -> alignment -> padding`` where the :class:`~.DAGCircuit` update only occurs at the end with the ``padding`` pass. @@ -59,7 +59,7 @@ features: The :class:`~.ConstrainedReschedule` pass considers both hardware alignment constraints that can be definied in a :class:`.BackendConfiguration` object, ``pulse_alignment`` and ``acquire_alignment``. This new class superscedes - the previosuly existing :class:`~.AlignMeasures` as it performs the same alignment + the previously existing :class:`~.AlignMeasures` as it performs the same alignment (via the property set) for measurement instructions in addition to general instruction alignment. By setting the ``acquire_alignment`` constraint argument for the :class:`~.ConstrainedReschedule` pass it is a drop-in replacement of @@ -67,7 +67,7 @@ features: - | Added two new transpiler passes :class:`~.ALAPScheduleAnalysis` and :class:`~.ASAPScheduleAnalysis` which superscede the :class:`~.ALAPSchedule` and :class:`~.ASAPSchedule` as part of the - reworked transpiler workflow for schedling. The new passes perform the same scheduling but + reworked transpiler workflow for scheduling. The new passes perform the same scheduling but in the property set and relying on a :class:`~.BasePadding` pass to adjust the circuit based on all the scheduling alignment analysis. @@ -155,5 +155,5 @@ features: Added a new transpiler pass :class:`~.PadDynamicalDecoupling` which superscedes the :class:`~.DynamicalDecoupling` pass as part of the reworked transpiler workflow for scheduling. This new pass will insert dynamical decoupling - sequences into the circuit per any scheduling and alignment analysis that occured in earlier + sequences into the circuit per any scheduling and alignment analysis that occurred in earlier passes. diff --git a/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml b/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml index 86e9c23612a0..93d39e481842 100644 --- a/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml +++ b/releasenotes/notes/0.20/vf2layout-preset-passmanager-db46513a24e79aa9.yaml @@ -18,7 +18,7 @@ upgrade: (where ``circuit.qubits[0]`` is mapped to physical qubit 0, ``circuit.qubits[1]`` is mapped to physical qubit 1, etc) assuming the trivial layout is perfect. If your use case was dependent on the - trivial layout you can explictly request it when transpiling by specifying + trivial layout you can explicitly request it when transpiling by specifying ``layout_method="trivial"`` when calling :func:`~qiskit.compiler.transpile`. - | The preset pass manager for optimization level 1 (when calling diff --git a/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml b/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml index 361f90cf35f4..182ccd32a791 100644 --- a/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml +++ b/releasenotes/notes/0.21/marginal-memory-29d9d6586ae78590.yaml @@ -3,7 +3,7 @@ features: - | Added a new function :func:`~.marginal_memory` which is used to marginalize shot memory arrays. Provided with the shot memory array and the indices - of interest, the function will return a maginized shot memory array. This + of interest, the function will return a marginalized shot memory array. This function differs from the memory support in the :func:`~.marginal_counts` method which only works on the ``memory`` field in a :class:`~.Results` object. diff --git a/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml b/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml index 5446d12e9ee6..e508f5b734fc 100644 --- a/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml +++ b/releasenotes/notes/0.21/vf2-post-layout-f0213e2c7ebb645c.yaml @@ -15,7 +15,7 @@ features: This pass is similar to the :class:`~.VF2Layout` pass and both internally use the same VF2 implementation from `retworkx `__. However, - :class:`~.VF2PostLayout` is deisgned to run after initial layout, routing, + :class:`~.VF2PostLayout` is designed to run after initial layout, routing, basis translation, and any optimization passes run and will only work if a layout has already been applied, the circuit has been routed, and all gates are in the target basis. This is required so that when a new layout diff --git a/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml b/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml index a6a8cbd06bd5..f79c4ade2c7c 100644 --- a/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml +++ b/releasenotes/notes/0.21/vqd-implementation-details-09b0ead8b42cacda.yaml @@ -2,7 +2,7 @@ features: - | The algorithm iteratively computes each eigenstate by starting from the ground - state (which is computed as in VQE) and then optimising a modified cost function + state (which is computed as in VQE) and then optimizing a modified cost function that tries to compute eigen states that are orthogonal to the states computed in the previous iterations and have the lowest energy when computed over the ansatz. The interface implemented is very similar to that of VQE and is of the form: diff --git a/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml b/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml index 5606c830b419..65d64dda08d3 100644 --- a/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml +++ b/releasenotes/notes/0.22/add-reverse-linear-entanglement-nlocal-38581e4ffb7a7c68.yaml @@ -13,5 +13,5 @@ upgrade: :class:`~.RealAmplitudes` and :class:`~.EfficientSU2` classes has changed from ``"full"`` to ``"reverse_linear"``. This change was made because the output circuit is equivalent but uses only :math:`n-1` instead of :math:`\frac{n(n-1)}{2}` :class:`~.CXGate` gates. If you - desire the previous default you can explicity set ``entanglement="full"`` when calling either + desire the previous default you can explicitly set ``entanglement="full"`` when calling either constructor. diff --git a/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml b/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml index 7f1e448fd188..9f535dcd409b 100644 --- a/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml +++ b/releasenotes/notes/0.22/fix-target-control-flow-representation-09520e2838f0657e.yaml @@ -93,7 +93,7 @@ upgrade: and no edges. This change was made to better reflect the actual connectivity constraints of the :class:`~.Target` because in this case there are no connectivity constraints on the backend being modeled by - the :class:`~.Target`, not a lack of connecitvity. If you desire the + the :class:`~.Target`, not a lack of connectivity. If you desire the previous behavior for any reason you can reproduce it by checking for a ``None`` return and manually building a coupling map, for example:: diff --git a/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml b/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml index a0a56ff0d85a..e471b6dc3717 100644 --- a/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml +++ b/releasenotes/notes/0.22/gate-direction-target-a9f0acd0cf30ed66.yaml @@ -2,5 +2,5 @@ fixes: - | The :class:`.GateDirection` transpiler pass will now respect the available - values for gate parameters when handling parametrised gates with a + values for gate parameters when handling parametrized gates with a :class:`.Target`. diff --git a/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml b/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml index d6267404c245..4ef32d600fc0 100644 --- a/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml +++ b/releasenotes/notes/0.22/primitive-run-5d1afab3655330a6.yaml @@ -3,7 +3,7 @@ features: - | Added new methods for executing primitives: :meth:`.BaseSampler.run` and :meth:`.BaseEstimator.run`. These methods execute asynchronously and return :class:`.JobV1` objects which - provide a handle to the exections. These new run methods can be passed :class:`~.QuantumCircuit` + provide a handle to the exceptions. These new run methods can be passed :class:`~.QuantumCircuit` objects (and observables for :class:`~.BaseEstimator`) that are not registered in the constructor. For example:: diff --git a/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml b/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml index 0162dfcad6a7..15bb07698849 100644 --- a/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml +++ b/releasenotes/notes/0.22/remove-symbolic-pulse-subclasses-77314a1654521852.yaml @@ -5,7 +5,7 @@ features: :class:`.Drag` and :class:`.Constant` have been upgraded to instantiate :class:`SymbolicPulse` rather than the subclass itself. All parametric pulse objects in pulse programs must be symbolic pulse instances, - because subclassing is no longer neccesary. Note that :class:`SymbolicPulse` can + because subclassing is no longer necessary. Note that :class:`SymbolicPulse` can uniquely identify a particular envelope with the symbolic expression object defined in :attr:`SymbolicPulse.envelope`. upgrade: @@ -15,7 +15,7 @@ upgrade: these pulse subclasses are no longer instantiated. They will still work in Terra 0.22, but you should begin transitioning immediately. Instead of using type information, :attr:`SymbolicPulse.pulse_type` should be used. - This is assumed to be a unique string identifer for pulse envelopes, + This is assumed to be a unique string identifier for pulse envelopes, and we can use string equality to investigate the pulse types. For example, .. code-block:: python diff --git a/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml b/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml index 9e5c3916c782..659b7312bb1a 100644 --- a/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml +++ b/releasenotes/notes/0.22/steppable-optimizers-9d9b48ba78bd58bb.yaml @@ -72,7 +72,7 @@ features: evaluated_gradient = grad(ask_data.x_center) optimizer.state.njev += 1 - optmizer.state.nit += 1 + optimizer.state.nit += 1 cf = TellData(eval_jac=evaluated_gradient) optimizer.tell(ask_data=ask_data, tell_data=tell_data) diff --git a/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml b/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml index 6061ff04b3d0..3da0de9d6db0 100644 --- a/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml +++ b/releasenotes/notes/0.22/tensored-subset-fitter-bd28e6e6ec5bdaae.yaml @@ -5,5 +5,5 @@ features: class. The implementation is restricted to mitigation patterns in which each qubit is mitigated individually, e.g. ``[[0], [1], [2]]``. This is, however, the most widely used case. It allows the :class:`.TensoredMeasFitter` to - be used in cases where the numberical order of the physical qubits does not + be used in cases where the numerical order of the physical qubits does not match the index of the classical bit. diff --git a/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml b/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml index 009984fbd7f0..92c07b4e2452 100644 --- a/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml +++ b/releasenotes/notes/0.22/visualization-reorganisation-9e302239705c7842.yaml @@ -2,7 +2,7 @@ upgrade: - | The visualization module :mod:`qiskit.visualization` has seen some internal - reorganisation. This should not have affected the public interface, but if + reorganization. This should not have affected the public interface, but if you were accessing any internals of the circuit drawers, they may now be in different places. The only parts of the visualization module that are considered public are the components that are documented in this online diff --git a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml index a9fa4703d678..f28178eb320e 100644 --- a/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml +++ b/releasenotes/notes/0.23/fix-qpy-loose-bits-5283dc4ad3823ce3.yaml @@ -1,9 +1,9 @@ --- fixes: - | - QPY deserialisation will no longer add extra :class:`.Clbit` instances to the + QPY deserialization will no longer add extra :class:`.Clbit` instances to the circuit if there are both loose :class:`.Clbit`\ s in the circuit and more :class:`~qiskit.circuit.Qubit`\ s than :class:`.Clbit`\ s. - | - QPY deserialisation will no longer add registers named `q` and `c` if the + QPY deserialization will no longer add registers named `q` and `c` if the input circuit contained only loose bits. diff --git a/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml b/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml index e1d083a4a2d0..716726dcbe5d 100644 --- a/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml +++ b/releasenotes/notes/0.23/fix_8897-2a90c4b0857c19c2.yaml @@ -2,7 +2,7 @@ fixes: - | Fixes issue where :meth:`.Statevector.evolve` and :meth:`.DensityMatrix.evolve` - would raise an exeception for nested subsystem evolution for non-qubit + would raise an exception for nested subsystem evolution for non-qubit subsystems. Fixes `issue #8897 `_ - | diff --git a/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml b/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml index 365c5858610c..d7dbd7edcb31 100644 --- a/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml +++ b/releasenotes/notes/0.23/initial_state-8e20b04fc2ec2f4b.yaml @@ -3,4 +3,4 @@ upgrade: - | The ``initial_state`` argument of the :class:`~NLocal` class should be a :class:`~.QuantumCircuit`. Passing any other type was deprecated as of Qiskit - Terra 0.18.0 (July 2021) and that posibility is now removed. + Terra 0.18.0 (July 2021) and that possibility is now removed. diff --git a/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml b/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml index 969b2aa2a24b..6d1d608aab72 100644 --- a/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml +++ b/releasenotes/notes/0.23/target-aware-optimize-1q-decomposition-cb9bb4651607b639.yaml @@ -3,6 +3,6 @@ features: - | The :class:`~.Optimize1qGatesDecomposition` transpiler pass has a new keyword argument, ``target``, on its constructor. This argument can be used to - specify a :class:`~.Target` object that represnts the compilation target. + specify a :class:`~.Target` object that represents the compilation target. If used it superscedes the ``basis`` argument to determine if an instruction in the circuit is present on the target backend. diff --git a/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml b/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml index c019f1329d0c..a755d3b95065 100644 --- a/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml +++ b/releasenotes/notes/0.23/target-aware-unroll-custom-definitions-a1b839de199ca048.yaml @@ -3,6 +3,6 @@ features: - | The :class:`~.UnrollCustomDefinitions` transpiler pass has a new keyword argument, ``target``, on its constructor. This argument can be used to - specify a :class:`~.Target` object that represnts the compilation target. - If used it superscedes the ``basis_gates`` argument to determine if an + specify a :class:`~.Target` object that represents the compilation target. + If used it supersedes the ``basis_gates`` argument to determine if an instruction in the circuit is present on the target backend. diff --git a/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml b/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml index ea98eddd2d68..7f4e7a64fcd3 100644 --- a/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml +++ b/releasenotes/notes/0.24/add-hls-plugins-038388970ad43c55.yaml @@ -65,7 +65,7 @@ features: qc.append(lin_fun, [0, 1, 2]) qc.append(cliff, [1, 2, 3]) - # Choose synthesis methods that adhere to linear-nearest-neighbour connectivity + # Choose synthesis methods that adhere to linear-nearest-neighbor connectivity hls_config = HLSConfig(linear_function=["kms"], clifford=["lnn"]) # Synthesize diff --git a/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml b/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml index 4072e8635383..55bd079d0f89 100644 --- a/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml +++ b/releasenotes/notes/0.24/add-new-symbolic-pulses-4dc46ecaaa1ba928.yaml @@ -9,5 +9,5 @@ features: * :class:``~qiskit.pulse.library.Triangle`` The new functions return a ``ScalableSymbolicPulse``. With the exception of the ``Sawtooth`` phase, - behaviour is identical to that of the corresponding waveform generators (:class:``~qiskit.pulse.library.sin`` etc). + behavior is identical to that of the corresponding waveform generators (:class:``~qiskit.pulse.library.sin`` etc). The ``Sawtooth`` phase is defined such that a phase of :math:``2\\pi`` shifts by a full cycle. diff --git a/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml b/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml index 93a0523397bd..85637311dc47 100644 --- a/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml +++ b/releasenotes/notes/0.24/deprecate-bip-mapping-f0025c4c724e1ec8.yaml @@ -10,5 +10,5 @@ deprecations: The pass was made into a separate plugin package for two reasons, first the dependency on CPLEX makes it harder to use and secondly the plugin - packge more cleanly integrates with :func:`~.transpile`. + package more cleanly integrates with :func:`~.transpile`. diff --git a/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml b/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml index e06fec8772f8..c992160b740e 100644 --- a/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml +++ b/releasenotes/notes/0.24/fix-setting-circuit-data-operation-1b8326b1b089f10c.yaml @@ -5,4 +5,4 @@ fixes: to be any object that implements :class:`.Operation`, not just a :class:`.circuit.Instruction`. Note that any manual mutation of :attr:`.QuantumCircuit.data` is discouraged; it is not *usually* any more efficient than building a new circuit object, as checking the invariants - surrounding parametrised objects can be surprisingly expensive. + surrounding parametrized objects can be surprisingly expensive. diff --git a/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml b/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml index 000dd81ee984..c217852c33c4 100644 --- a/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml +++ b/releasenotes/notes/0.24/fix-tensoredop-to-matrix-6f22644f1bdb8b41.yaml @@ -2,6 +2,6 @@ fixes: - | Fixed a bug in :meth:`.TensoredOp.to_matrix` where the global coefficient of the operator - was multiplied to the final matrix more than once. Now, the global coefficient is correclty + was multiplied to the final matrix more than once. Now, the global coefficient is correctly applied, independent of the number of tensored operators or states. Fixed `#9398 `__. diff --git a/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml b/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml index f33b3d240ba2..79fe381d896e 100644 --- a/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml +++ b/releasenotes/notes/0.24/include-ecr-gates-for-pulse-scaling-8369eb584c6d8fe1.yaml @@ -5,4 +5,4 @@ features: and RZXCalibrationBuilderNoEcho to consume `ecr` entangling gates from the backend, in addition to the `cx` gates they were build for. These native gates contain the calibrated pulse schedules that the pulse scaling passes use to - generate arbitraty rotations of the :class:`~RZXGate` operation. + generate arbitrary rotations of the :class:`~RZXGate` operation. diff --git a/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml b/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml index c6170b62a8fa..c2003139d67f 100644 --- a/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml +++ b/releasenotes/notes/0.24/qasm2-exporter-rewrite-8993dd24f930b180.yaml @@ -21,7 +21,7 @@ fixes: `#7769 `__ and `#7773 `__. - | - Standard gates defined by Qiskit, such as :class:`.RZXGate`, will now have properly parametrised + Standard gates defined by Qiskit, such as :class:`.RZXGate`, will now have properly parametrized definitions when exported using the OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`). See `#7172 `__. - | diff --git a/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml b/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml index f38d694e77ad..0fc7dd6b69ac 100644 --- a/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml +++ b/releasenotes/notes/0.24/qasm2-parser-rust-ecf6570e2d445a94.yaml @@ -17,7 +17,7 @@ features: This new parser is approximately 10x faster than the existing ones at :meth:`.QuantumCircuit.from_qasm_file` and :meth:`.QuantumCircuit.from_qasm_str` for large files, - and has less overhead on each call as well. The new parser is more extensible, customisable and + and has less overhead on each call as well. The new parser is more extensible, customizable and generally also more type-safe; it will not attempt to output custom Qiskit objects when the definition in the OpenQASM 2 file clashes with the Qiskit object, unlike the current exporter. See the :mod:`qiskit.qasm2` module documentation for full details and more examples. diff --git a/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml b/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml index fadd99c80234..9af85d8572da 100644 --- a/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml +++ b/releasenotes/notes/0.24/vqd-list-initial-points-list-optimizers-033d7439f86bbb71.yaml @@ -5,6 +5,6 @@ features: to pass a list of optimizers and initial points for the different minimization runs. For example, the ``k``-th initial point and ``k``-th optimizer will be used for the optimization of the - ``k-1``-th exicted state. + ``k-1``-th excited state. diff --git a/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml b/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml index d5dfd69f54aa..999d30095d12 100644 --- a/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml +++ b/releasenotes/notes/0.25/dag-substitute-node-propagate-condition-898052b53edb1f17.yaml @@ -3,7 +3,7 @@ features: - | :meth:`.DAGCircuit.substitute_node` gained a ``propagate_condition`` keyword argument that is analogous to the same argument in :meth:`~.DAGCircuit.substitute_node_with_dag`. Setting this - to ``False`` opts out of the legacy behaviour of copying a condition on the ``node`` onto the + to ``False`` opts out of the legacy behavior of copying a condition on the ``node`` onto the new ``op`` that is replacing it. This option is ignored for general control-flow operations, which will never propagate their diff --git a/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml b/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml index c8f7e3ddd8c2..4902fb85a2fd 100644 --- a/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml +++ b/releasenotes/notes/0.25/faster-parameter-rebind-3c799e74456469d9.yaml @@ -15,6 +15,6 @@ features: reduce the overhead of input normalisation in this function. fixes: - | - A parametrised circuit that contains a custom gate whose definition has a parametrised global phase + A parametrized circuit that contains a custom gate whose definition has a parametrized global phase can now successfully bind the parameter in the inner global phase. See `#10283 `__ for more detail. diff --git a/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml index aa89abeb662d..30d76baa4364 100644 --- a/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml +++ b/releasenotes/notes/0.25/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml @@ -4,4 +4,4 @@ fixes: Fixed the gate decomposition of multi-controlled Z rotation gates added via :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled phase gate, which has a relative phase difference to the Z rotation. To obtain the - previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`. + previous `.QuantumCircuit.mcrz` behavior, use `.QuantumCircuit.mcp`. diff --git a/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml b/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml index 890223e82b8c..40975c31d1d2 100644 --- a/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml +++ b/releasenotes/notes/0.25/fix_9016-2e8bc2cb10b5e204.yaml @@ -3,5 +3,5 @@ fixes: - | When the parameter ``conditional=True`` is set in ``qiskit.circuit.random.random_circuit``, the conditional operations will - be preceded by a full mid-circuit measurment. + be preceded by a full mid-circuit measurement. Fixes `#9016 `__ diff --git a/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml b/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml index ebb699e8c7b1..246387384ca4 100644 --- a/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml +++ b/releasenotes/notes/0.25/flatten-nlocal-family-292b23b99947f3c9.yaml @@ -17,6 +17,6 @@ features: :class:`~.circuit.Instruction` objects. While this isn't optimal for visualization it typically results in much better runtime performance, especially with :meth:`.QuantumCircuit.bind_parameters` and - :meth:`.QuantumCircuit.assign_parameters` which can see a substatial + :meth:`.QuantumCircuit.assign_parameters` which can see a substantial runtime improvement with a flattened output compared to the nested wrapped default output. diff --git a/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml b/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml index e460b6e55ee1..6615b11605ef 100644 --- a/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml +++ b/releasenotes/notes/0.25/normalize-stateprep-e21972dce8695509.yaml @@ -4,5 +4,5 @@ features: The instructions :class:`.StatePreparation` and :class:`~.extensions.Initialize`, and their associated circuit methods :meth:`.QuantumCircuit.prepare_state` and :meth:`~.QuantumCircuit.initialize`, gained a keyword argument ``normalize``, which can be set to ``True`` to automatically normalize - an array target. By default this is ``False``, which retains the current behaviour of + an array target. By default this is ``False``, which retains the current behavior of raising an exception when given non-normalized input. diff --git a/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml b/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml index 35ce0ce4ca57..9ca56d961bc6 100644 --- a/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml +++ b/releasenotes/notes/0.25/qpy-layout-927ab34f2b47f4aa.yaml @@ -7,6 +7,6 @@ upgrade: fixes: - | Fixed the :mod:`~qiskit.qpy` serialization of :attr:`.QuantumCircuit.layout` - attribue. Previously, the :attr:`~.QuantumCircuit.layout` attribute would + attribute. Previously, the :attr:`~.QuantumCircuit.layout` attribute would have been dropped when serializing a circuit to QPY. Fixed `#10112 `__ diff --git a/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml b/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml index fdfaf394e8b0..844deea0b804 100644 --- a/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml +++ b/releasenotes/notes/0.25/token-swapper-rustworkx-9e02c0ab67a59fe8.yaml @@ -2,5 +2,5 @@ upgrade: - | The :meth:`~.ApproximateTokenSwapper.map` has been modified to use the new ``rustworkx`` version - of :func:`~graph_token_swapper` for performance reasons. Qiskit Terra 0.25 now requires versison + of :func:`~graph_token_swapper` for performance reasons. Qiskit Terra 0.25 now requires version 0.13.0 of ``rustworkx``. diff --git a/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml b/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml index 4126b91bb869..17bf77f51c1b 100644 --- a/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml +++ b/releasenotes/notes/0.45/dag-appenders-check-84d4ef20c1e20fd0.yaml @@ -4,5 +4,5 @@ features: The :class:`.DAGCircuit` methods :meth:`~.DAGCircuit.apply_operation_back` and :meth:`~.DAGCircuit.apply_operation_front` have gained a ``check`` keyword argument that can be set ``False`` to skip validation that the inputs uphold the :class:`.DAGCircuit` data-structure - invariants. This is useful as a performance optimisation when the DAG is being built from + invariants. This is useful as a performance optimization when the DAG is being built from known-good data, such as during transpiler passes. diff --git a/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml b/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml index a62ed79a0471..a55582f9476c 100644 --- a/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml +++ b/releasenotes/notes/0.45/deprecate-duplicates-a871f83bbbe1c96f.yaml @@ -13,5 +13,5 @@ deprecations: * :meth:`.QuantumCircuit.i` in favor of :meth:`.QuantumCircuit.id` Note that :meth:`.QuantumCircuit.i` is the only exception to the rule above, but since - :meth:`.QuantumCircuit.id` more intuively represents the identity and is used more, we chose + :meth:`.QuantumCircuit.id` more intuitively represents the identity and is used more, we chose it over its counterpart. \ No newline at end of file diff --git a/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml b/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml index c3d4bbe63861..2819be565c1d 100644 --- a/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml +++ b/releasenotes/notes/0.45/discrete-basis-gatedirection-bdffad3b47c1c532.yaml @@ -4,5 +4,5 @@ fixes: The :class:`.GateDirection` transpiler pass will now use discrete-basis translations rather than relying on a continuous :class:`.RYGate`, which should help make some discrete-basis-set targets slightly more reliable. In general, :func:`.transpile` only has partial support for basis sets - that do not contain a continuously-parametrised operation, and so it may not always succeed in + that do not contain a continuously-parametrized operation, and so it may not always succeed in these situations, and will almost certainly not produce optimal results. diff --git a/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml b/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml index 234888b2ac9b..335c819dc8ce 100644 --- a/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml +++ b/releasenotes/notes/0.45/expr-rvalue-conditions-8b5d5f7c015658c0.yaml @@ -60,7 +60,7 @@ features: and :class:`.Clbit` instances. All these classical expressions are fully supported through the Qiskit transpiler stack, through - QPY serialisation (:mod:`qiskit.qpy`) and for export to OpenQASM 3 (:mod:`qiskit.qasm3`). Import + QPY serialization (:mod:`qiskit.qpy`) and for export to OpenQASM 3 (:mod:`qiskit.qasm3`). Import from OpenQASM 3 is currently managed by `a separate package `__ (which is re-exposed via :mod:`qiskit.qasm3`), which we hope will be extended to match the new features in Qiskit. diff --git a/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml b/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml index e03fa8555a48..8f04a25141e0 100644 --- a/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml +++ b/releasenotes/notes/0.45/fix-parameter-hash-d22c270090ffc80e.yaml @@ -3,8 +3,8 @@ features: - | :class:`.Parameter` now has an advanced-usage keyword argument ``uuid`` in its constructor, which can be used to make the :class:`.Parameter` compare equal to another of the same name. - This should not typically be used by users, and is most useful for custom serialisation and - deserialisation. + This should not typically be used by users, and is most useful for custom serialization and + deserialization. fixes: - | The hash of a :class:`.Parameter` is now equal to the hashes of any diff --git a/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml b/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml index e93e62c963a3..e75c8e096062 100644 --- a/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml +++ b/releasenotes/notes/0.45/fix-timeline-draw-unscheduled-warning-873f7a24c6b51e2c.yaml @@ -4,7 +4,7 @@ deprecations: Passing a circuit to :func:`qiskit.visualization.timeline_drawer` that does not have scheduled node start-time information is deprecated. Only circuits that have gone through one of the scheduling analysis passes (for example :class:`.ALAPScheduleAnalysis` or - :class:`.ASAPScheduleAnalysis`) can be visualised. If you have used one of the old-style + :class:`.ASAPScheduleAnalysis`) can be visualized. If you have used one of the old-style scheduling passes (for example :class:`.ALAPSchedule` or :class:`.ASAPSchedule`), you can propagate the scheduling information by running:: @@ -18,5 +18,5 @@ deprecations: instruction_durations=InstructionDurations(), ) - This behaviour was previously intended to be deprecated in Qiskit 0.37, but due to a bug in the - warning, it was not displayed to users until now. The behaviour will be removed in Qiskit 1.0. + This behavior was previously intended to be deprecated in Qiskit 0.37, but due to a bug in the + warning, it was not displayed to users until now. The behavior will be removed in Qiskit 1.0. diff --git a/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml b/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml index befe3011c701..a1e4a5492667 100644 --- a/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml +++ b/releasenotes/notes/0.45/qasm2-new-api-4e1e4803d6a5a175.yaml @@ -18,7 +18,7 @@ features: (:mod:`qiskit.qasm3`) and QPY (:mod:`qiskit.qpy`) modules. This is particularly important since the method name :meth:`~.QuantumCircuit.qasm` gave no indication of the OpenQASM version, and since it was originally - added, Qiskit has gained several serialisation modules that could easily + added, Qiskit has gained several serialization modules that could easily become confused. deprecations: - | diff --git a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml index 7b8313e6b080..ac38751d9cee 100644 --- a/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml +++ b/releasenotes/notes/0.45/singletons-83782de8bd062cbc.yaml @@ -103,7 +103,7 @@ features: :attr:`.Instruction.mutable` which is used to get a mutable copy and check whether an :class:`~.circuit.Instruction` object is mutable. With the introduction of :class:`~.SingletonGate` these methods can be used to have a unified interface - to deal with the mutablitiy of instruction objects. + to deal with the mutability of instruction objects. - | Added an attribute :attr:`.Instruction.base_class`, which gets the "base" type of an instruction. Many instructions will satisfy ``type(obj) == obj.base_class``, however the diff --git a/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml b/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml index 151127a0c8de..a6a8745d1754 100644 --- a/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml +++ b/releasenotes/notes/0.9/changes-on-upgrade-6fcd573269a8ebc5.yaml @@ -121,7 +121,7 @@ other: ``qiskit.execute()`` has been changed to optimization level 1 pass manager defined at ``qiskit.transpile.preset_passmanagers.level1_pass_manager``. - | - All the circuit drawer backends now willl express gate parameters in a + All the circuit drawer backends now will express gate parameters in a circuit as common fractions of pi in the output visualization. If the value of a parameter can be expressed as a fraction of pi that will be used instead of the numeric equivalent. diff --git a/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml b/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml index 925da8840f5b..30b7baca7839 100644 --- a/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml +++ b/releasenotes/notes/0.9/new-features-0.9-159645f977a139f7.yaml @@ -38,7 +38,7 @@ features: Two new functions, ``sech()`` and ``sech_deriv()`` were added to the pulse library module ``qiskit.pulse.pulse_lib`` for creating an unnormalized hyperbolic secant ``SamplePulse`` object and an unnormalized hyperbolic - secant derviative ``SamplePulse`` object respectively. + secant derivative ``SamplePulse`` object respectively. - | A new kwarg option ``vertical_compression`` was added to the ``QuantumCircuit.draw()`` method and the @@ -61,7 +61,7 @@ features: - | When creating a PassManager you can now specify a callback function that if specified will be run after each pass is executed. This function gets - passed a set of kwargs on each call with the state of the pass maanger after + passed a set of kwargs on each call with the state of the pass manager after each pass execution. Currently these kwargs are: * pass\_ (Pass): the pass being run diff --git a/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml b/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml index 62626e809957..1b11fe945425 100644 --- a/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml +++ b/releasenotes/notes/1.0/remove-opflow-qi-utils-3debd943c65b17da.yaml @@ -8,7 +8,7 @@ upgrade_algorithms: - | - A series of legacy quantum execution utililties have been removed, following their deprecation in Qiskit 0.44. + A series of legacy quantum execution utilities have been removed, following their deprecation in Qiskit 0.44. These include the ``qiskit.utils.QuantumInstance`` class, as well the modules: - ``qiskit.utils.backend_utils`` diff --git a/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml b/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml index a86869a4e540..62e7e9451619 100644 --- a/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml +++ b/releasenotes/notes/1.1/fix-qdrift-evolution-bceb9c4f182ab0f5.yaml @@ -1,3 +1,3 @@ fixes: - | - Fix incorrect implemention of `qDRIFT`, negative coeffients of the Hamiltonian are now added back whereas they were always forced to be positive. + Fix incorrect implemention of `qDRIFT`, negative coefficients of the Hamiltonian are now added back whereas they were always forced to be positive. diff --git a/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml b/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml index 0bf60329a230..ff83deee9392 100644 --- a/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml +++ b/releasenotes/notes/1.1/star-prerouting-0998b59880c20cef.yaml @@ -3,7 +3,7 @@ features: - | Added a new transpiler pass :class:`.StarPreRouting` which is designed to identify star connectivity subcircuits and then replace them with an optimal linear routing. This is useful for certain circuits that are composed of - this circuit connectivity such as Berstein Vazirani and QFT. For example: + this circuit connectivity such as Bernstein Vazirani and QFT. For example: .. plot: diff --git a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml index 73da8e6b7ad3..6fa548d9245c 100644 --- a/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml +++ b/releasenotes/notes/outcome_bitstring_target_for_probabilities_dict-e53f524d115bbcfc.yaml @@ -5,7 +5,7 @@ features: :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 + at worst case calculate (2^n) number of probability 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 diff --git a/setup.py b/setup.py index 38af5286e817..61168050547c 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ # # python setup.py build_rust --inplace --release # -# to make optimised Rust components even for editable releases, which would otherwise be quite +# to make optimized Rust components even for editable releases, which would otherwise be quite # unergonomic to do otherwise. diff --git a/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm b/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm index 4910c35dfec6..ba5db1397046 100644 --- a/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm +++ b/test/benchmarks/qasm/54QBT_25CYC_QSE_3.qasm @@ -1,7 +1,7 @@ // Originally source from the QUEKO benchmark suite // https://github.com/UCLA-VAST/QUEKO-benchmark // A benchmark that is near-term feasible for Google Sycamore with a optimal -// soluation depth of 25 +// solution depth of 25 OPENQASM 2.0; include "qelib1.inc"; qreg q[54]; diff --git a/test/python/circuit/classical/test_expr_constructors.py b/test/python/circuit/classical/test_expr_constructors.py index 10cef88122c1..012697a17dd0 100644 --- a/test/python/circuit/classical/test_expr_constructors.py +++ b/test/python/circuit/classical/test_expr_constructors.py @@ -224,7 +224,7 @@ def test_binary_bitwise_explicit(self, function, opcode): ) @ddt.unpack def test_binary_bitwise_uint_inference(self, function, opcode): - """The binary bitwise functions have specialised inference for the widths of integer + """The binary bitwise functions have specialized inference for the widths of integer literals, since the bitwise functions require the operands to already be of exactly the same width without promotion.""" cr = ClassicalRegister(8, "c") @@ -247,7 +247,7 @@ def test_binary_bitwise_uint_inference(self, function, opcode): ), ) - # Inference between two integer literals is "best effort". This behaviour isn't super + # Inference between two integer literals is "best effort". This behavior isn't super # important to maintain if we want to change the expression system. self.assertEqual( function(5, 255), diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index 6e1dcf77e1ef..625db22cc12d 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -85,7 +85,7 @@ def test_var_equality(self): self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) # Manually constructing the same object with the same UUID should cause it compare equal, - # though, for serialisation ease. + # though, for serialization ease. self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) # This is a badly constructed variable because it's using a different type to refer to the diff --git a/test/python/circuit/library/test_diagonal.py b/test/python/circuit/library/test_diagonal.py index 7dde0d62d8ff..b1158fdab3f0 100644 --- a/test/python/circuit/library/test_diagonal.py +++ b/test/python/circuit/library/test_diagonal.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the digonal circuit.""" +"""Test the diagonal circuit.""" import unittest from ddt import ddt, data diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 3d85bb526dc2..078b5af04ea9 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -183,7 +183,7 @@ def __init__(self, *_args, **_kwargs): raise self # We don't want to issue a warning on mutation until we know that the values are - # finalised; this is because a user might want to mutate the number of qubits and the + # finalized; this is because a user might want to mutate the number of qubits and the # approximation degree. In these cases, wait until we try to build the circuit. with warnings.catch_warnings(record=True) as caught_warnings: warnings.filterwarnings( diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 216ea3a59bb0..04e71a0dd4d7 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -1163,7 +1163,7 @@ def test_qpy_with_for_loop_iterator(self): self.assertDeprecatedBitProperties(qc, new_circuit) def test_qpy_clbit_switch(self): - """Test QPY serialisation for a switch statement with a Clbit target.""" + """Test QPY serialization for a switch statement with a Clbit target.""" case_t = QuantumCircuit(2, 1) case_t.x(0) case_f = QuantumCircuit(2, 1) @@ -1180,7 +1180,7 @@ def test_qpy_clbit_switch(self): self.assertDeprecatedBitProperties(qc, new_circuit) def test_qpy_register_switch(self): - """Test QPY serialisation for a switch statement with a ClassicalRegister target.""" + """Test QPY serialization for a switch statement with a ClassicalRegister target.""" qreg = QuantumRegister(2, "q") creg = ClassicalRegister(3, "c") diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index e9a7416f78c4..517a7093e81e 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -928,7 +928,7 @@ def test_remove_final_measurements_7089(self): self.assertEqual(circuit.clbits, []) def test_remove_final_measurements_bit_locations(self): - """Test remove_final_measurements properly recalculates clbit indicies + """Test remove_final_measurements properly recalculates clbit indices and preserves order of remaining cregs and clbits. """ c0 = ClassicalRegister(1) diff --git a/test/python/circuit/test_circuit_vars.py b/test/python/circuit/test_circuit_vars.py index 8b7167eed7e1..f6916dcb72db 100644 --- a/test/python/circuit/test_circuit_vars.py +++ b/test/python/circuit/test_circuit_vars.py @@ -76,7 +76,7 @@ def test_initialise_declarations_mapping(self): ) def test_initialise_declarations_dependencies(self): - """Test that the cirucit initialiser can take in declarations with dependencies between + """Test that the circuit initializer can take in declarations with dependencies between them, provided they're specified in a suitable order.""" a = expr.Var.new("a", types.Bool()) vars_ = [ diff --git a/test/python/circuit/test_control_flow_builders.py b/test/python/circuit/test_control_flow_builders.py index 0aeeb084f479..e41b5c1f9f31 100644 --- a/test/python/circuit/test_control_flow_builders.py +++ b/test/python/circuit/test_control_flow_builders.py @@ -1438,7 +1438,7 @@ def test_break_continue_deeply_nested(self, loop_operation): These are the deepest tests, hitting all parts of the deferred builder scopes. We test ``if``, ``if/else`` and ``switch`` paths at various levels of the scoping to try and account - for as many weird edge cases with the deferred behaviour as possible. We try to make sure, + for as many weird edge cases with the deferred behavior as possible. We try to make sure, particularly in the most complicated examples, that there are resources added before and after every single scope, to try and catch all possibilities of where resources may be missed. @@ -2943,7 +2943,7 @@ def test_inplace_compose_within_builder(self): self.assertEqual(canonicalize_control_flow(outer), canonicalize_control_flow(expected)) def test_global_phase_of_blocks(self): - """It should be possible to set a global phase of a scope independantly of the containing + """It should be possible to set a global phase of a scope independently of the containing scope and other sibling scopes.""" qr = QuantumRegister(3) cr = ClassicalRegister(3) @@ -3335,7 +3335,7 @@ def test_if_rejects_break_continue_if_not_in_loop(self): def test_for_rejects_reentry(self): """Test that the ``for``-loop context manager rejects attempts to re-enter it. Since it holds some forms of state during execution (the loop variable, which may be generated), we - can't safely re-enter it and get the expected behaviour.""" + can't safely re-enter it and get the expected behavior.""" for_manager = QuantumCircuit(2, 2).for_loop(range(2)) with for_manager: @@ -3584,7 +3584,7 @@ def test_reject_c_if_from_outside_scope(self): # As a side-effect of how the lazy building of 'if' statements works, we actually # *could* add a condition to the gate after the 'if' block as long as we were still # within the 'for' loop. It should actually manage the resource correctly as well, but - # it's "undefined behaviour" than something we specifically want to forbid or allow. + # it's "undefined behavior" than something we specifically want to forbid or allow. test = QuantumCircuit(bits) with test.for_loop(range(2)): with test.if_test(cond): diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index ced7229415ea..8ba70ee852ca 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -1262,7 +1262,7 @@ def test_modify_cugate_params_slice(self): self.assertEqual(cu.base_gate.params, [0.4, 0.3, 0.2]) def test_assign_nested_controlled_cu(self): - """Test assignment of an arbitrary controlled parametrised gate that appears through the + """Test assignment of an arbitrary controlled parametrized gate that appears through the `Gate.control()` method on an already-controlled gate.""" theta = Parameter("t") qc_c = QuantumCircuit(2) diff --git a/test/python/circuit/test_instructions.py b/test/python/circuit/test_instructions.py index dbda9262f15b..170b47632c4d 100644 --- a/test/python/circuit/test_instructions.py +++ b/test/python/circuit/test_instructions.py @@ -566,7 +566,7 @@ def case(specifier, message): case(1.0, r"Unknown classical resource specifier: .*") def test_instructionset_c_if_with_no_requester(self): - """Test that using a raw :obj:`.InstructionSet` with no classical-resource resoluer accepts + """Test that using a raw :obj:`.InstructionSet` with no classical-resource resolver accepts arbitrary :obj:`.Clbit` and `:obj:`.ClassicalRegister` instances, but rejects integers.""" with self.subTest("accepts arbitrary register"): diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index feab3002f582..0095f87be9ae 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -314,7 +314,7 @@ def test_assign_parameters_by_name(self): ) def test_bind_parameters_custom_definition_global_phase(self): - """Test that a custom gate with a parametrised `global_phase` is assigned correctly.""" + """Test that a custom gate with a parametrized `global_phase` is assigned correctly.""" x = Parameter("x") custom = QuantumCircuit(1, global_phase=x).to_gate() base = QuantumCircuit(1) @@ -1485,7 +1485,7 @@ def test_cast_to_float_when_underlying_expression_bound(self): def test_cast_to_float_intermediate_complex_value(self): """Verify expression can be cast to a float when it is fully bound, but an intermediate part of the expression evaluation involved complex types. Sympy is generally more permissive - than symengine here, and sympy's tends to be the expected behaviour for our users.""" + than symengine here, and sympy's tends to be the expected behavior for our users.""" x = Parameter("x") bound_expr = (x + 1.0 + 1.0j).bind({x: -1.0j}) self.assertEqual(float(bound_expr), 1.0) @@ -2166,7 +2166,7 @@ def test_parameter_equal_to_identical_expression(self): self.assertEqual(theta, expr) def test_parameter_symbol_equal_after_ufunc(self): - """Verfiy ParameterExpression phi + """Verify ParameterExpression phi and ParameterExpression cos(phi) have the same symbol map""" phi = Parameter("phi") cos_phi = numpy.cos(phi) diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 630db1c0c1c0..1a6e5b6b8fe5 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -233,7 +233,7 @@ def test_assemble_opaque_inst(self): self.assertEqual(qobj.experiments[0].instructions[0].params, [0.5, 0.4]) def test_assemble_unroll_parametervector(self): - """Verfiy that assemble unrolls parametervectors ref #5467""" + """Verify that assemble unrolls parametervectors ref #5467""" pv1 = ParameterVector("pv1", 3) pv2 = ParameterVector("pv2", 3) qc = QuantumCircuit(2, 2) @@ -609,7 +609,7 @@ def test_pulse_gates_common_cals(self): self.assertFalse(hasattr(qobj.experiments[1].config, "calibrations")) def test_assemble_adds_circuit_metadata_to_experiment_header(self): - """Verify that any circuit metadata is added to the exeriment header.""" + """Verify that any circuit metadata is added to the experiment header.""" circ = QuantumCircuit(2, metadata={"experiment_type": "gst", "execution_number": "1234"}) qobj = assemble(circ, shots=100, memory=False, seed_simulator=6) self.assertEqual( @@ -943,7 +943,7 @@ def setUp(self): self.header = {"backend_name": "FakeOpenPulse2Q", "backend_version": "0.0.0"} def test_assemble_adds_schedule_metadata_to_experiment_header(self): - """Verify that any circuit metadata is added to the exeriment header.""" + """Verify that any circuit metadata is added to the experiment header.""" self.schedule.metadata = {"experiment_type": "gst", "execution_number": "1234"} qobj = assemble( self.schedule, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 6058f922e171..77b63a3098bc 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -499,7 +499,7 @@ def test_transpile_bell(self): self.assertIsInstance(circuits, QuantumCircuit) def test_transpile_bell_discrete_basis(self): - """Test that it's possible to transpile a very simple circuit to a discrete stabiliser-like + """Test that it's possible to transpile a very simple circuit to a discrete stabilizer-like basis. In general, we do not make any guarantees about the possibility or quality of transpilation in these situations, but this is at least useful as a check that stuff that _could_ be possible remains so.""" @@ -1890,7 +1890,7 @@ def test_transpile_control_flow_no_backend(self, opt_level): @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): - """Test transpile() with a target and constrol flow ops.""" + """Test transpile() with a target and control flow ops.""" target = GenericBackendV2(num_qubits=8, control_flow=True).target circuit = QuantumCircuit(6, 1) @@ -1927,7 +1927,7 @@ def test_transpile_with_custom_control_flow_target(self, opt_level): transpiled = transpile( circuit, optimization_level=opt_level, target=target, seed_transpiler=12434 ) - # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; + # Tests of the complete validity of a circuit are mostly done at the individual pass level; # here we're just checking that various passes do appear to have run. self.assertIsInstance(transpiled, QuantumCircuit) # Assert layout ran. diff --git a/test/python/converters/test_circuit_to_instruction.py b/test/python/converters/test_circuit_to_instruction.py index d4b69e71aa10..e3239d4b5ff4 100644 --- a/test/python/converters/test_circuit_to_instruction.py +++ b/test/python/converters/test_circuit_to_instruction.py @@ -226,7 +226,7 @@ def test_forbids_captured_vars(self): qc.to_instruction() def test_forbids_input_vars(self): - """This test can be relaxed when we have proper support for the behaviour. + """This test can be relaxed when we have proper support for the behavior. This actually has a natural meaning; the input variables could become typed parameters. We don't have a formal structure for managing that yet, though, so it's forbidden until the @@ -236,7 +236,7 @@ def test_forbids_input_vars(self): qc.to_instruction() def test_forbids_declared_vars(self): - """This test can be relaxed when we have proper support for the behaviour. + """This test can be relaxed when we have proper support for the behavior. This has a very natural representation, which needs basically zero special handling, since the variables are necessarily entirely internal to the subroutine. The reason it is diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py index 2fe3e4bad7bf..b2715078d7f5 100644 --- a/test/python/dagcircuit/test_collect_blocks.py +++ b/test/python/dagcircuit/test_collect_blocks.py @@ -163,7 +163,7 @@ def test_collect_and_split_gates_from_dagcircuit(self): self.assertEqual(len(split_blocks), 3) def test_collect_and_split_gates_from_dagdependency(self): - """Test collecting and splitting blocks from DAGDependecy.""" + """Test collecting and splitting blocks from DAGDependency.""" qc = QuantumCircuit(6) qc.cx(0, 1) qc.cx(3, 5) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 0fcff29e5b40..4ab4e392cbb1 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -853,7 +853,7 @@ def test_quantum_successors(self): self.assertIsInstance(cnot_node.op, CXGate) successor_cnot = self.dag.quantum_successors(cnot_node) - # Ordering between Reset and out[q1] is indeterminant. + # Ordering between Reset and out[q1] is indeterminate. successor1 = next(successor_cnot) successor2 = next(successor_cnot) @@ -902,7 +902,7 @@ def test_quantum_predecessors(self): self.assertIsInstance(cnot_node.op, CXGate) predecessor_cnot = self.dag.quantum_predecessors(cnot_node) - # Ordering between Reset and in[q1] is indeterminant. + # Ordering between Reset and in[q1] is indeterminate. predecessor1 = next(predecessor_cnot) predecessor2 = next(predecessor_cnot) @@ -1611,7 +1611,7 @@ def setUp(self): qc.h(0) qc.measure(0, 0) # The depth of an if-else is the path through the longest block (regardless of the - # condition). The size is the sum of both blocks (mostly for optimisation-target purposes). + # condition). The size is the sum of both blocks (mostly for optimization-target purposes). with qc.if_test((qc.clbits[0], True)) as else_: qc.x(1) qc.cx(2, 3) @@ -2420,11 +2420,11 @@ def test_raise_if_var_mismatch(self): def test_raise_if_substituting_dag_modifies_its_conditional(self): """Verify that we raise if the input dag modifies any of the bits in node.op.condition.""" - # The `propagate_condition=True` argument (and behaviour of `substitute_node_with_dag` + # The `propagate_condition=True` argument (and behavior of `substitute_node_with_dag` # before the parameter was added) treats the replacement DAG as implementing only the # un-controlled operation. The original contract considers it an error to replace a node # with an operation that may modify one of the condition bits in case this affects - # subsequent operations, so when `propagate_condition=True`, this error behaviour is + # subsequent operations, so when `propagate_condition=True`, this error behavior is # maintained. instr = Instruction("opaque", 1, 1, []) diff --git a/test/python/primitives/containers/test_observables_array.py b/test/python/primitives/containers/test_observables_array.py index 5a8513a5ed9c..ea51718aebea 100644 --- a/test/python/primitives/containers/test_observables_array.py +++ b/test/python/primitives/containers/test_observables_array.py @@ -112,7 +112,7 @@ def test_coerce_observable_zero_sparse_pauli_op(self): self.assertEqual(obs["Z"], 1) def test_coerce_observable_duplicate_sparse_pauli_op(self): - """Test coerce_observable for SparsePauliOp wiht duplicate paulis""" + """Test coerce_observable for SparsePauliOp with duplicate paulis""" op = qi.SparsePauliOp(["XX", "-XX", "XX", "-XX"], [2, 1, 3, 2]) obs = ObservablesArray.coerce_observable(op) self.assertIsInstance(obs, dict) diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index 80045dee0d67..535841cc90f7 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -346,9 +346,9 @@ class TestObservableValidation(QiskitTestCase): ), ) @unpack - def test_validate_observables(self, obsevables, expected): - """Test obsevables standardization.""" - self.assertEqual(validation._validate_observables(obsevables), expected) + def test_validate_observables(self, observables, expected): + """Test observables standardization.""" + self.assertEqual(validation._validate_observables(observables), expected) @data(None, "ERROR") def test_qiskit_error(self, observables): @@ -358,7 +358,7 @@ def test_qiskit_error(self, observables): @data((), []) def test_value_error(self, observables): - """Test value error if no obsevables are provided.""" + """Test value error if no observables are provided.""" with self.assertRaises(ValueError): validation._validate_observables(observables) diff --git a/test/python/primitives/test_statevector_estimator.py b/test/python/primitives/test_statevector_estimator.py index 117ead6717ad..1ed2d42e0e3b 100644 --- a/test/python/primitives/test_statevector_estimator.py +++ b/test/python/primitives/test_statevector_estimator.py @@ -276,7 +276,7 @@ def test_precision_seed(self): result = job.result() np.testing.assert_allclose(result[0].data.evs, [1.901141473854881]) np.testing.assert_allclose(result[1].data.evs, [1.901141473854881]) - # precision=0 impliese the exact expectation value + # precision=0 implies the exact expectation value job = estimator.run([(psi1, hamiltonian1, [theta1])], precision=0) result = job.result() np.testing.assert_allclose(result[0].data.evs, [1.5555572817900956]) diff --git a/test/python/pulse/test_instruction_schedule_map.py b/test/python/pulse/test_instruction_schedule_map.py index bf56f980a729..67628ba845ae 100644 --- a/test/python/pulse/test_instruction_schedule_map.py +++ b/test/python/pulse/test_instruction_schedule_map.py @@ -342,7 +342,7 @@ def test_sequenced_parameterized_schedule(self): self.assertEqual(sched.instructions[2][-1].phase, 3) def test_schedule_generator(self): - """Test schedule generator functionalty.""" + """Test schedule generator functionality.""" dur_val = 10 amp = 1.0 @@ -364,7 +364,7 @@ def test_func(dur: int): self.assertEqual(inst_map.get_parameters("f", (0,)), ("dur",)) def test_schedule_generator_supports_parameter_expressions(self): - """Test expression-based schedule generator functionalty.""" + """Test expression-based schedule generator functionality.""" t_param = Parameter("t") amp = 1.0 diff --git a/test/python/pulse/test_reference.py b/test/python/pulse/test_reference.py index c33d7588e920..3d7603461756 100644 --- a/test/python/pulse/test_reference.py +++ b/test/python/pulse/test_reference.py @@ -87,7 +87,7 @@ def test_refer_schedule_parameter_scope(self): self.assertEqual(sched_z1.parameters, sched_y1.parameters) def test_refer_schedule_parameter_assignment(self): - """Test assigning to parametr in referenced schedule""" + """Test assigning to parameter in referenced schedule""" param = circuit.Parameter("name") with pulse.build() as sched_x1: @@ -197,7 +197,7 @@ def test_calling_similar_schedule(self): """Test calling schedules with the same representation. sched_x1 and sched_y1 are the different subroutines, but same representation. - Two references shoud be created. + Two references should be created. """ param1 = circuit.Parameter("param") param2 = circuit.Parameter("param") @@ -539,7 +539,7 @@ def test_lazy_ecr(self): def test_cnot(self): """Integration test with CNOT schedule construction.""" - # echeod cross resonance + # echoed cross resonance with pulse.build(name="ecr", default_alignment="sequential") as ecr_sched: pulse.call(self.cr_sched, name="cr") pulse.call(self.xp_sched, name="xp") diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 135e874be488..048c5d7852b4 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -495,7 +495,7 @@ def test_unbound_circuit(self): self.assertEqual(Exporter().dumps(qc), expected_qasm) def test_unknown_parameterized_gate_called_multiple_times(self): - """Test that a parameterised gate is called correctly if the first instance of it is + """Test that a parameterized gate is called correctly if the first instance of it is generic.""" x, y = Parameter("x"), Parameter("y") qc = QuantumCircuit(2) @@ -1310,7 +1310,7 @@ def test_chain_else_if(self): "", ] ) - # This is not the default behaviour, and it's pretty buried how you'd access it. + # This is not the default behavior, and it's pretty buried how you'd access it. builder = QASM3Builder( qc, includeslist=("stdgates.inc",), @@ -1370,7 +1370,7 @@ def test_chain_else_if_does_not_chain_if_extra_instructions(self): "", ] ) - # This is not the default behaviour, and it's pretty buried how you'd access it. + # This is not the default behavior, and it's pretty buried how you'd access it. builder = QASM3Builder( qc, includeslist=("stdgates.inc",), @@ -1935,7 +1935,7 @@ def test_var_naming_clash_gate(self): class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCase): """Test functionality that is not what we _want_, but is what we need to do while the definition - of custom gates with parameterisation does not work correctly. + of custom gates with parameterization does not work correctly. These tests are modified versions of those marked with the `requires_fixed_parameterisation` decorator, and this whole class can be deleted once those are fixed. See gh-7335. diff --git a/test/python/qasm3/test_import.py b/test/python/qasm3/test_import.py index 85da3f41933e..522f68c89562 100644 --- a/test/python/qasm3/test_import.py +++ b/test/python/qasm3/test_import.py @@ -13,7 +13,7 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring # Since the import is nearly entirely delegated to an external package, most of the testing is done -# there. Here we need to test our wrapping behaviour for base functionality and exceptions. We +# there. Here we need to test our wrapping behavior for base functionality and exceptions. We # don't want to get into a situation where updates to `qiskit_qasm3_import` breaks Terra's test # suite due to too specific tests on the Terra side. diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index 0ef7079f461a..8c96f63c4ddf 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -2119,7 +2119,7 @@ def qubitwise_commutes(left: Pauli, right: Pauli) -> bool: pauli_list = PauliList(input_labels) groups = pauli_list.group_qubit_wise_commuting() - # checking that every input Pauli in pauli_list is in a group in the ouput + # checking that every input Pauli in pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group] self.assertListEqual(sorted(output_labels), sorted(input_labels)) @@ -2153,7 +2153,7 @@ def commutes(left: Pauli, right: Pauli) -> bool: # if qubit_wise=True, equivalent to test_group_qubit_wise_commuting groups = pauli_list.group_commuting(qubit_wise=False) - # checking that every input Pauli in pauli_list is in a group in the ouput + # checking that every input Pauli in pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group] self.assertListEqual(sorted(output_labels), sorted(input_labels)) # Within each group, every operator commutes with every other operator. 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 e7a9b89b7318..c4f09ec2d795 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 @@ -181,7 +181,7 @@ def test_from_index_list(self): self.assertEqual(spp_op.paulis, PauliList(expected_labels)) def test_from_index_list_parameters(self): - """Test from_list method specifying the Paulis via indices with paramteres.""" + """Test from_list method specifying the Paulis via indices with parameters.""" expected_labels = ["XXZ", "IXI", "YIZ", "III"] paulis = ["XXZ", "X", "YZ", ""] indices = [[2, 1, 0], [1], [2, 0], []] @@ -1028,7 +1028,7 @@ def commutes(left: Pauli, right: Pauli, qubit_wise: bool) -> bool: coeffs = np.random.random(len(input_labels)) + np.random.random(len(input_labels)) * 1j sparse_pauli_list = SparsePauliOp(input_labels, coeffs) groups = sparse_pauli_list.group_commuting(qubit_wise) - # checking that every input Pauli in sparse_pauli_list is in a group in the ouput + # checking that every input Pauli in sparse_pauli_list is in a group in the output output_labels = [pauli.to_label() for group in groups for pauli in group.paulis] self.assertListEqual(sorted(output_labels), sorted(input_labels)) # checking that every coeffs are grouped according to sparse_pauli_list group @@ -1056,7 +1056,7 @@ def commutes(left: Pauli, right: Pauli, qubit_wise: bool) -> bool: ) def test_dot_real(self): - """Test dot for real coefficiets.""" + """Test dot for real coefficients.""" x = SparsePauliOp("X", np.array([1])) y = SparsePauliOp("Y", np.array([1])) iz = SparsePauliOp("Z", 1j) diff --git a/test/python/result/test_mitigators.py b/test/python/result/test_mitigators.py index 66662bb587e1..3b3e83bce009 100644 --- a/test/python/result/test_mitigators.py +++ b/test/python/result/test_mitigators.py @@ -215,7 +215,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - f"Mitigator {mitigator} did not correctly marganalize for qubits 1,2", + f"Mitigator {mitigator} did not correctly marginalize for qubits 1,2", ) mitigated_probs_02 = ( @@ -227,7 +227,7 @@ def test_clbits_parameter(self): self.assertLess( mitigated_error, 0.001, - f"Mitigator {mitigator} did not correctly marganalize for qubits 0,2", + f"Mitigator {mitigator} did not correctly marginalize for qubits 0,2", ) def test_qubits_parameter(self): diff --git a/test/python/result/test_result.py b/test/python/result/test_result.py index 7d73ab2ebcf6..ff1f4cbf29a0 100644 --- a/test/python/result/test_result.py +++ b/test/python/result/test_result.py @@ -105,7 +105,7 @@ def test_counts_duplicate_name(self): result.get_counts("foo") def test_result_repr(self): - """Test that repr is contstructed correctly for a results object.""" + """Test that repr is constructed correctly for a results object.""" raw_counts = {"0x0": 4, "0x2": 10} data = models.ExperimentResultData(counts=raw_counts) exp_result_header = QobjExperimentHeader( diff --git a/test/python/synthesis/test_clifford_decompose_layers.py b/test/python/synthesis/test_clifford_decompose_layers.py index 19183ce17303..1db810621e32 100644 --- a/test/python/synthesis/test_clifford_decompose_layers.py +++ b/test/python/synthesis/test_clifford_decompose_layers.py @@ -53,7 +53,7 @@ def test_decompose_clifford(self, num_qubits): @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): - """Test layered decomposition for linear-nearest-neighbour (LNN) connectivity.""" + """Test layered decomposition for linear-nearest-neighbor (LNN) connectivity.""" rng = np.random.default_rng(1234) samples = 10 for _ in range(samples): @@ -64,7 +64,7 @@ def test_decompose_lnn_depth(self, num_qubits): filter_function=lambda x: x.operation.num_qubits == 2 ) self.assertTrue(depth2q <= 7 * num_qubits + 2) - # Check that the Clifford circuit has linear nearest neighbour connectivity + # Check that the Clifford circuit has linear nearest neighbor connectivity self.assertTrue(check_lnn_connectivity(circ.decompose())) cliff_target = Clifford(circ) self.assertEqual(cliff, cliff_target) diff --git a/test/python/synthesis/test_cx_cz_synthesis.py b/test/python/synthesis/test_cx_cz_synthesis.py index ef7eeb38b8f6..63353dab95df 100644 --- a/test/python/synthesis/test_cx_cz_synthesis.py +++ b/test/python/synthesis/test_cx_cz_synthesis.py @@ -34,7 +34,7 @@ class TestCXCZSynth(QiskitTestCase): @combine(num_qubits=[3, 4, 5, 6, 7, 8, 9, 10]) def test_cx_cz_synth_lnn(self, num_qubits): - """Test the CXCZ synthesis code for linear nearest neighbour connectivity.""" + """Test the CXCZ synthesis code for linear nearest neighbor connectivity.""" seed = 1234 rng = np.random.default_rng(seed) num_gates = 10 diff --git a/test/python/synthesis/test_cz_synthesis.py b/test/python/synthesis/test_cz_synthesis.py index 7284039e56c8..af663a4f0d32 100644 --- a/test/python/synthesis/test_cz_synthesis.py +++ b/test/python/synthesis/test_cz_synthesis.py @@ -31,7 +31,7 @@ class TestCZSynth(QiskitTestCase): @combine(num_qubits=[3, 4, 5, 6, 7]) def test_cz_synth_lnn(self, num_qubits): - """Test the CZ synthesis code for linear nearest neighbour connectivity.""" + """Test the CZ synthesis code for linear nearest neighbor connectivity.""" seed = 1234 rng = np.random.default_rng(seed) num_gates = 10 diff --git a/test/python/synthesis/test_stabilizer_synthesis.py b/test/python/synthesis/test_stabilizer_synthesis.py index e195c9cf2707..958faa204c11 100644 --- a/test/python/synthesis/test_stabilizer_synthesis.py +++ b/test/python/synthesis/test_stabilizer_synthesis.py @@ -54,7 +54,7 @@ def test_decompose_stab(self, num_qubits): @combine(num_qubits=[4, 5, 6, 7]) def test_decompose_lnn_depth(self, num_qubits): - """Test stabilizer state decomposition for linear-nearest-neighbour (LNN) connectivity.""" + """Test stabilizer state decomposition for linear-nearest-neighbor (LNN) connectivity.""" rng = np.random.default_rng(1234) samples = 10 for _ in range(samples): @@ -66,7 +66,7 @@ def test_decompose_lnn_depth(self, num_qubits): filter_function=lambda x: x.operation.num_qubits == 2 ) self.assertTrue(depth2q == 2 * num_qubits + 2) - # Check that the stabilizer state circuit has linear nearest neighbour connectivity + # Check that the stabilizer state circuit has linear nearest neighbor connectivity self.assertTrue(check_lnn_connectivity(circ.decompose())) stab_target = StabilizerState(circ) # Verify that the two stabilizers generate the same state diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py index 8d953ff1b83c..025b9accf227 100644 --- a/test/python/synthesis/test_synthesis.py +++ b/test/python/synthesis/test_synthesis.py @@ -219,7 +219,7 @@ def check_two_qubit_weyl_specialization( ): """Check that the two qubit Weyl decomposition gets specialized as expected""" - # Loop to check both for implicit and explicity specialization + # Loop to check both for implicit and explicitly specialization for decomposer in (TwoQubitWeylDecomposition, expected_specialization): if isinstance(decomposer, TwoQubitWeylDecomposition): with self.assertDebugOnly(): diff --git a/test/python/test_util.py b/test/python/test_util.py index f807f0d4e255..d403ed004bc3 100644 --- a/test/python/test_util.py +++ b/test/python/test_util.py @@ -43,7 +43,7 @@ def test_local_hardware_no_cpu_count(self): self.assertEqual(1, result["cpus"]) def test_local_hardware_no_sched_five_count(self): - """Test cpu cound if sched affinity method is missing and cpu count is 5.""" + """Test cpu could if sched affinity method is missing and cpu count is 5.""" with mock.patch.object(multiprocessing, "os", spec=[]): multiprocessing.os.cpu_count = mock.MagicMock(return_value=5) del multiprocessing.os.sched_getaffinity @@ -51,7 +51,7 @@ def test_local_hardware_no_sched_five_count(self): self.assertEqual(2, result["cpus"]) def test_local_hardware_no_sched_sixty_four_count(self): - """Test cpu cound if sched affinity method is missing and cpu count is 64.""" + """Test cpu could if sched affinity method is missing and cpu count is 64.""" with mock.patch.object(multiprocessing, "os", spec=[]): multiprocessing.os.cpu_count = mock.MagicMock(return_value=64) del multiprocessing.os.sched_getaffinity diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index c1df8adbad2c..c95d65422d4b 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -406,7 +406,7 @@ def test_valid_pulse_duration(self): self.pulse_gate_validation_pass(circuit) def test_no_calibration(self): - """No error raises if no calibration is addedd.""" + """No error raises if no calibration is added.""" circuit = QuantumCircuit(1) circuit.x(0) diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index 206a652299c2..ff8be63ffbcd 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -119,7 +119,7 @@ def test_can_combine_cliffords(self): cliff2 = self.create_cliff2() cliff3 = self.create_cliff3() - # Create a circuit with two consective cliffords + # Create a circuit with two consecutive cliffords qc1 = QuantumCircuit(4) qc1.append(cliff1, [3, 1, 2]) qc1.append(cliff2, [3, 1, 2]) diff --git a/test/python/transpiler/test_commutative_cancellation.py b/test/python/transpiler/test_commutative_cancellation.py index 1030b83ae2ac..71bab61708cd 100644 --- a/test/python/transpiler/test_commutative_cancellation.py +++ b/test/python/transpiler/test_commutative_cancellation.py @@ -198,7 +198,7 @@ def test_control_bit_of_cnot(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot1(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[Z]------.-- qr0:---[Z]--- | | @@ -219,7 +219,7 @@ def test_control_bit_of_cnot1(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot2(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[T]------.-- qr0:---[T]--- | | @@ -240,7 +240,7 @@ def test_control_bit_of_cnot2(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot3(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[Rz]------.-- qr0:---[Rz]--- | | @@ -261,7 +261,7 @@ def test_control_bit_of_cnot3(self): self.assertEqual(expected, new_circuit) def test_control_bit_of_cnot4(self): - """A simple circuit where the two cnots shoule be cancelled. + """A simple circuit where the two cnots should be cancelled. qr0:----.------[T]------.-- qr0:---[T]--- | | @@ -662,7 +662,7 @@ def test_basis_global_phase_02(self): self.assertEqual(Operator(circ), Operator(ccirc)) def test_basis_global_phase_03(self): - """Test global phase preservation if cummulative z-rotation is 0""" + """Test global phase preservation if cumulative z-rotation is 0""" circ = QuantumCircuit(1) circ.rz(np.pi / 2, 0) circ.p(np.pi / 2, 0) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 9b34d095b3b1..8a11af2bd688 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -517,7 +517,7 @@ def test_inverted_order(self): # The first two 'if' blocks here represent exactly the same operation as each other on the # outer bits, because in the second, the bit-order of the block is reversed, but so is the # order of the bits in the outer circuit that they're bound to, which makes them the same. - # The second two 'if' blocks also represnt the same operation as each other, but the 'first + # The second two 'if' blocks also represent the same operation as each other, but the 'first # two' and 'second two' pairs represent qubit-flipped operations. qc.if_test((0, False), body.copy(), qc.qubits, qc.clbits) qc.if_test((0, False), body.reverse_bits(), reversed(qc.qubits), qc.clbits) diff --git a/test/python/transpiler/test_full_ancilla_allocation.py b/test/python/transpiler/test_full_ancilla_allocation.py index 73d9708d0ba6..452d9d93965a 100644 --- a/test/python/transpiler/test_full_ancilla_allocation.py +++ b/test/python/transpiler/test_full_ancilla_allocation.py @@ -194,7 +194,7 @@ def test_name_collision(self): ) def test_bad_layout(self): - """Layout referes to a register that do not exist in the circuit""" + """Layout refers to a register that do not exist in the circuit""" qr = QuantumRegister(3, "q") circ = QuantumCircuit(qr) dag = circuit_to_dag(circ) diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 1e0f19b1a331..569a210f8a9d 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -342,7 +342,7 @@ def test_symmetric_gates(self, gate): self.assertEqual(pass_(circuit), expected) def test_target_parameter_any(self): - """Test that a parametrised 2q gate is replaced correctly both if available and not + """Test that a parametrized 2q gate is replaced correctly both if available and not available.""" circuit = QuantumCircuit(2) circuit.rzx(1.5, 0, 1) @@ -356,7 +356,7 @@ def test_target_parameter_any(self): self.assertNotEqual(GateDirection(None, target=swapped)(circuit), circuit) def test_target_parameter_exact(self): - """Test that a parametrised 2q gate is detected correctly both if available and not + """Test that a parametrized 2q gate is detected correctly both if available and not available.""" circuit = QuantumCircuit(2) circuit.rzx(1.5, 0, 1) diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index f20b102d1838..ff54169374bc 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -2045,7 +2045,7 @@ def test_unroll_empty_definition_with_phase(self): self.assertEqual(pass_(qc), expected) def test_leave_store_alone_basis(self): - """Don't attempt to synthesise `Store` instructions with basis gates.""" + """Don't attempt to synthesize `Store` instructions with basis gates.""" pass_ = HighLevelSynthesis(equivalence_library=std_eqlib, basis_gates=["u", "cx"]) @@ -2068,7 +2068,7 @@ def test_leave_store_alone_basis(self): self.assertEqual(pass_(qc), expected) def test_leave_store_alone_with_target(self): - """Don't attempt to synthesise `Store` instructions with a `Target`.""" + """Don't attempt to synthesize `Store` instructions with a `Target`.""" # Note no store. target = Target() diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index bd14891bb8c4..1431449779b3 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -98,7 +98,7 @@ def test_valid_pulse_duration(self): pm.run(circuit) def test_no_calibration(self): - """No error raises if no calibration is addedd.""" + """No error raises if no calibration is added.""" circuit = QuantumCircuit(1) circuit.x(0) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index c00208a2ffaf..ee85dc34ffd6 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1110,7 +1110,7 @@ def test_1(self, circuit, level): self.assertIn("swap", resulting_basis) # Skipping optimization level 3 because the swap gates get absorbed into - # a unitary block as part of the KAK decompostion optimization passes and + # a unitary block as part of the KAK decomposition optimization passes and # optimized away. @combine( level=[0, 1, 2], @@ -1487,7 +1487,7 @@ def _define(self): optimization_level=optimization_level, seed_transpiler=2022_10_04, ) - # Tests of the complete validity of a circuit are mostly done at the indiviual pass level; + # Tests of the complete validity of a circuit are mostly done at the individual pass level; # here we're just checking that various passes do appear to have run. self.assertIsInstance(transpiled, QuantumCircuit) # Assert layout ran. diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 487fbf9daef1..0a7b977162a3 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -358,7 +358,7 @@ def test_dual_ghz_with_wide_barrier(self): self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8]) def test_dual_ghz_with_intermediate_barriers(self): - """Test dual ghz circuit with intermediate barriers local to each componennt.""" + """Test dual ghz circuit with intermediate barriers local to each component.""" qc = QuantumCircuit(8, name="double dhz") qc.h(0) qc.cz(0, 1) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index fbe4e1fbf74c..b1effdae7d8b 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -241,7 +241,7 @@ def test_do_not_reorder_measurements(self): self.assertIsInstance(second_measure.operation, Measure) # Assert that the first measure is on the same qubit that the HGate was applied to, and the # second measurement is on a different qubit (though we don't care which exactly - that - # depends a little on the randomisation of the pass). + # depends a little on the randomization of the pass). self.assertEqual(last_h.qubits, first_measure.qubits) self.assertNotEqual(last_h.qubits, second_measure.qubits) diff --git a/test/python/transpiler/test_template_matching.py b/test/python/transpiler/test_template_matching.py index 1e4da01cb426..d7c4baa18fd9 100644 --- a/test/python/transpiler/test_template_matching.py +++ b/test/python/transpiler/test_template_matching.py @@ -43,7 +43,7 @@ def _ry_to_rz_template_pass(parameter: Parameter = None, extra_costs=None): - """Create a simple pass manager that runs a template optimisation with a single transformation. + """Create a simple pass manager that runs a template optimization with a single transformation. It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where if ``parameter`` is given, it will be the instance used in the template.""" if parameter is None: @@ -409,7 +409,7 @@ def test_optimizer_does_not_replace_unbound_partial_match(self): circuit_out = PassManager(pass_).run(circuit_in) - # The template optimisation should not have replaced anything, because + # The template optimization should not have replaced anything, because # that would require it to leave dummy parameters in place without # binding them. self.assertEqual(circuit_in, circuit_out) diff --git a/test/python/transpiler/test_token_swapper.py b/test/python/transpiler/test_token_swapper.py index 9ded634eba75..8a3a8c72ee2d 100644 --- a/test/python/transpiler/test_token_swapper.py +++ b/test/python/transpiler/test_token_swapper.py @@ -67,7 +67,7 @@ def test_small(self) -> None: self.assertEqual({i: i for i in range(8)}, permutation) def test_bug1(self) -> None: - """Tests for a bug that occured in happy swap chains of length >2.""" + """Tests for a bug that occurred in happy swap chains of length >2.""" graph = rx.PyGraph() graph.extend_from_edge_list( [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (3, 6)] diff --git a/test/python/transpiler/test_unitary_synthesis_plugin.py b/test/python/transpiler/test_unitary_synthesis_plugin.py index ceca591ce087..f6790e8ed14d 100644 --- a/test/python/transpiler/test_unitary_synthesis_plugin.py +++ b/test/python/transpiler/test_unitary_synthesis_plugin.py @@ -71,7 +71,7 @@ class ControllableSynthesis(UnitarySynthesisPlugin): """A dummy synthesis plugin, which can have its ``supports_`` properties changed to test different parts of the synthesis plugin interface. By default, it accepts all keyword arguments and accepts all number of qubits, but if its run method is called, it just returns ``None`` to - indicate that the gate should not be synthesised.""" + indicate that the gate should not be synthesized.""" min_qubits = None max_qubits = None @@ -153,7 +153,7 @@ def mock_default_run_method(self): # We need to mock out DefaultUnitarySynthesis.run, except it will actually get called as an # instance method, so we can't just wrap the method defined on the class, but instead we # need to wrap a method that has been bound to a particular instance. This is slightly - # frgaile, because we're likely wrapping a _different_ instance, but since there are no + # fragile, because we're likely wrapping a _different_ instance, but since there are no # arguments to __init__, and no internal state, it should be ok. It doesn't matter if we # dodged the patching of the manager class that happens elsewhere in this test suite, # because we're always accessing something that the patch would delegate to the inner diff --git a/test/python/utils/test_lazy_loaders.py b/test/python/utils/test_lazy_loaders.py index bd63d7ff04ac..11b37ccb9d15 100644 --- a/test/python/utils/test_lazy_loaders.py +++ b/test/python/utils/test_lazy_loaders.py @@ -423,7 +423,7 @@ def exec_module(self, module): def test_import_allows_attributes_failure(self): """Check that the import tester can accept a dictionary mapping module names to attributes, - and that these are recognised when they are missing.""" + and that these are recognized when they are missing.""" # We can just use existing modules for this. name_map = { "sys": ("executable", "path"), diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 2a0a61c7904b..e7d28aac8a9e 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -5626,7 +5626,7 @@ def test_draw_hamiltonian_single(self): self.assertEqual(circuit.draw(output="text").single_string(), expected) def test_draw_hamiltonian_multi(self): - """Text Hamiltonian gate with mutiple qubits.""" + """Text Hamiltonian gate with multiple qubits.""" expected = "\n".join( [ " ┌──────────────┐", @@ -5647,7 +5647,7 @@ def test_draw_hamiltonian_multi(self): class TestTextPhase(QiskitTestCase): - """Testing the draweing a circuit with phase""" + """Testing the drawing a circuit with phase""" def test_bell(self): """Text Bell state with phase.""" diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index f2ce2bee1086..cc70cccf7d4d 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -975,7 +975,7 @@ def load_qpy(qpy_files, version_parts): def _main(): - parser = argparse.ArgumentParser(description="Test QPY backwards compatibilty") + parser = argparse.ArgumentParser(description="Test QPY backwards compatibility") parser.add_argument("command", choices=["generate", "load"]) parser.add_argument( "--version", diff --git a/test/utils/_canonical.py b/test/utils/_canonical.py index 367281f512c9..b05254b3e7bc 100644 --- a/test/utils/_canonical.py +++ b/test/utils/_canonical.py @@ -52,7 +52,7 @@ def canonicalize_control_flow(circuit: QuantumCircuit) -> QuantumCircuit: """Canonicalize all control-flow operations in a circuit. This is not an efficient operation, and does not affect any properties of the circuit. Its - intent is to normalise parts of circuits that have a non-deterministic construction. These are + intent is to normalize parts of circuits that have a non-deterministic construction. These are the ordering of bit arguments in control-flow blocks output by the builder interface, and automatically generated ``for``-loop variables. From 591260f0694fb1944507283ce10cf8f068bd3935 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 20 Jun 2024 11:09:00 +0200 Subject: [PATCH 157/179] use compose instead of + (#12609) --- qiskit/circuit/library/n_local/two_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/n_local/two_local.py b/qiskit/circuit/library/n_local/two_local.py index 1cb388349fec..f3822d53243f 100644 --- a/qiskit/circuit/library/n_local/two_local.py +++ b/qiskit/circuit/library/n_local/two_local.py @@ -110,7 +110,7 @@ class TwoLocal(NLocal): >>> entangler_map = [[0, 3], [0, 2]] # entangle the first and last two-way >>> two = TwoLocal(4, [], 'cry', entangler_map, reps=1) - >>> circuit = two + two + >>> circuit = two.compose(two) >>> print(circuit.decompose().draw()) # note, that the parameters are the same! q_0: ─────■───────────■───────────■───────────■────── │ │ │ │ From 7d1731b60d8dd6219a292dc62f24d1a8d780e43a Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 20 Jun 2024 22:01:56 +0900 Subject: [PATCH 158/179] Fix v2 pulse drawer (#12608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix error when V2 model is set * Apply suggestions from code review * Fix black --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> Co-authored-by: Elena Peña Tapia --- qiskit/visualization/pulse_v2/device_info.py | 89 ++++++++++++------- .../fix-v2-pulse-drawer-d05e4e392766909f.yaml | 7 ++ 2 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml diff --git a/qiskit/visualization/pulse_v2/device_info.py b/qiskit/visualization/pulse_v2/device_info.py index 1e809c43abd2..7898f978772c 100644 --- a/qiskit/visualization/pulse_v2/device_info.py +++ b/qiskit/visualization/pulse_v2/device_info.py @@ -40,7 +40,7 @@ class :py:class:``DrawerBackendInfo`` with necessary methods to generate drawing from qiskit import pulse from qiskit.providers import BackendConfigurationError -from qiskit.providers.backend import Backend +from qiskit.providers.backend import Backend, BackendV2 class DrawerBackendInfo(ABC): @@ -106,40 +106,67 @@ def create_from_backend(cls, backend: Backend): Returns: OpenPulseBackendInfo: New configured instance. """ - configuration = backend.configuration() - defaults = backend.defaults() - - # load name - name = backend.name() - - # load cycle time - dt = configuration.dt - - # load frequencies chan_freqs = {} - - chan_freqs.update( - {pulse.DriveChannel(qind): freq for qind, freq in enumerate(defaults.qubit_freq_est)} - ) - chan_freqs.update( - {pulse.MeasureChannel(qind): freq for qind, freq in enumerate(defaults.meas_freq_est)} - ) - for qind, u_lo_mappers in enumerate(configuration.u_channel_lo): - temp_val = 0.0 + 0.0j - for u_lo_mapper in u_lo_mappers: - temp_val += defaults.qubit_freq_est[u_lo_mapper.q] * u_lo_mapper.scale - chan_freqs[pulse.ControlChannel(qind)] = temp_val.real - - # load qubit channel mapping qubit_channel_map = defaultdict(list) - for qind in range(configuration.n_qubits): - qubit_channel_map[qind].append(configuration.drive(qubit=qind)) - qubit_channel_map[qind].append(configuration.measure(qubit=qind)) - for tind in range(configuration.n_qubits): + + if hasattr(backend, "configuration") and hasattr(backend, "defaults"): + configuration = backend.configuration() + defaults = backend.defaults() + + name = configuration.backend_name + dt = configuration.dt + + # load frequencies + chan_freqs.update( + { + pulse.DriveChannel(qind): freq + for qind, freq in enumerate(defaults.qubit_freq_est) + } + ) + chan_freqs.update( + { + pulse.MeasureChannel(qind): freq + for qind, freq in enumerate(defaults.meas_freq_est) + } + ) + for qind, u_lo_mappers in enumerate(configuration.u_channel_lo): + temp_val = 0.0 + 0.0j + for u_lo_mapper in u_lo_mappers: + temp_val += defaults.qubit_freq_est[u_lo_mapper.q] * u_lo_mapper.scale + chan_freqs[pulse.ControlChannel(qind)] = temp_val.real + + # load qubit channel mapping + for qind in range(configuration.n_qubits): + qubit_channel_map[qind].append(configuration.drive(qubit=qind)) + qubit_channel_map[qind].append(configuration.measure(qubit=qind)) + for tind in range(configuration.n_qubits): + try: + qubit_channel_map[qind].extend(configuration.control(qubits=(qind, tind))) + except BackendConfigurationError: + pass + elif isinstance(backend, BackendV2): + # Pure V2 model doesn't contain channel frequency information. + name = backend.name + dt = backend.dt + + # load qubit channel mapping + for qind in range(backend.num_qubits): + # channels are NotImplemented by default so we must catch arbitrary error. + try: + qubit_channel_map[qind].append(backend.drive_channel(qind)) + except Exception: # pylint: disable=broad-except + pass try: - qubit_channel_map[qind].extend(configuration.control(qubits=(qind, tind))) - except BackendConfigurationError: + qubit_channel_map[qind].append(backend.measure_channel(qind)) + except Exception: # pylint: disable=broad-except pass + for tind in range(backend.num_qubits): + try: + qubit_channel_map[qind].extend(backend.control_channel(qubits=(qind, tind))) + except Exception: # pylint: disable=broad-except + pass + else: + raise RuntimeError("Backend object not yet supported") return OpenPulseBackendInfo( name=name, dt=dt, channel_frequency_map=chan_freqs, qubit_channel_map=qubit_channel_map diff --git a/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml b/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml new file mode 100644 index 000000000000..b158703c6b89 --- /dev/null +++ b/releasenotes/notes/fix-v2-pulse-drawer-d05e4e392766909f.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed a bug in :func:`qiskit.visualization.pulse_v2.interface.draw` that didn't + draw pulse schedules when the draw function was called with a :class:`.BackendV2` argument. + Because the V2 backend doesn't report hardware channel frequencies, + the generated drawing will show 'no freq.' below each channel label. From b6c61661272c2e242963c416e64a9a2e050ea25d Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 20 Jun 2024 18:15:25 +0100 Subject: [PATCH 159/179] Invalidate `parameters` cache on circuit copy (#12619) Previously, the caching of the parameter view could persist between copies of the circuit because it was part of the `copy.copy`. --- qiskit/circuit/quantumcircuit.py | 2 ++ .../fix-parameter-cache-05eac2f24477ccb8.yaml | 7 ++++++ test/python/circuit/test_parameters.py | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index a88dfd43ea4b..ee52e3308a94 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3674,6 +3674,8 @@ def copy_empty_like( cpy._data = CircuitData( self._data.qubits, self._data.clbits, global_phase=self._data.global_phase ) + # Invalidate parameters caching. + cpy._parameters = None cpy._calibrations = _copy.deepcopy(self._calibrations) cpy._metadata = _copy.deepcopy(self._metadata) diff --git a/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml b/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml new file mode 100644 index 000000000000..05ac759569f7 --- /dev/null +++ b/releasenotes/notes/fix-parameter-cache-05eac2f24477ccb8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + The :attr:`.QuantumCircuit.parameters` attribute will now correctly be empty + when using :meth:`.QuantumCircuit.copy_empty_like` on a parametric circuit. + Previously, an internal cache would be copied over without invalidation. + Fix `#12617 `__. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 0095f87be9ae..f841f969c00c 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -203,6 +203,29 @@ def test_parameters_property_by_index(self): for i, vi in enumerate(v): self.assertEqual(vi, qc.parameters[i]) + def test_parameters_property_independent_after_copy(self): + """Test that any `parameters` property caching is invalidated after a copy operation.""" + a = Parameter("a") + b = Parameter("b") + c = Parameter("c") + + qc1 = QuantumCircuit(1) + qc1.rz(a, 0) + self.assertEqual(set(qc1.parameters), {a}) + + qc2 = qc1.copy_empty_like() + self.assertEqual(set(qc2.parameters), set()) + + qc3 = qc1.copy() + self.assertEqual(set(qc3.parameters), {a}) + qc3.rz(b, 0) + self.assertEqual(set(qc3.parameters), {a, b}) + self.assertEqual(set(qc1.parameters), {a}) + + qc1.rz(c, 0) + self.assertEqual(set(qc1.parameters), {a, c}) + self.assertEqual(set(qc3.parameters), {a, b}) + def test_get_parameter(self): """Test the `get_parameter` method.""" x = Parameter("x") From b8de17f9082089275ae979c21cf03b8e99e245b3 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 21 Jun 2024 11:15:07 +0100 Subject: [PATCH 160/179] Localise `py_op` caching data in `RefCell` (#12594) This localises the caching concerns of the `PackedInstruction::py_op` field into a method `unpack_py_op`, which can now update the cache through an immutable reference (if no other immutable references are taken out). Having the new method to encapsulate the `cache_pyops` feature simplifies the large `#[cfg(feature = "cache_pyop")]` gates. --- crates/circuit/src/circuit_data.rs | 363 ++-------------------- crates/circuit/src/circuit_instruction.rs | 82 ++++- 2 files changed, 102 insertions(+), 343 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index da35787e3207..07f4579a4cd3 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -10,10 +10,13 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +#[cfg(feature = "cache_pygates")] +use std::cell::RefCell; + use crate::bit_data::BitData; use crate::circuit_instruction::{ - convert_py_to_operation_type, operation_type_and_data_to_py, CircuitInstruction, - ExtraInstructionAttributes, OperationInput, PackedInstruction, + convert_py_to_operation_type, CircuitInstruction, ExtraInstructionAttributes, OperationInput, + PackedInstruction, }; use crate::imports::{BUILTIN_LIST, QUBIT}; use crate::interner::{IndexedInterner, Interner, InternerKey}; @@ -489,66 +492,40 @@ impl CircuitData { .getattr(intern!(py, "deepcopy"))?; for inst in &mut res.data { match &mut inst.op { - OperationType::Standard(_) => { - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } - } + OperationType::Standard(_) => {} OperationType::Gate(ref mut op) => { op.gate = deepcopy.call1((&op.gate,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Instruction(ref mut op) => { op.instruction = deepcopy.call1((&op.instruction,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Operation(ref mut op) => { op.operation = deepcopy.call1((&op.operation,))?.unbind(); - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } }; + #[cfg(feature = "cache_pygates")] + { + *inst.py_op.borrow_mut() = None; + } } } else if copy_instructions { for inst in &mut res.data { match &mut inst.op { - OperationType::Standard(_) => { - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } - } + OperationType::Standard(_) => {} OperationType::Gate(ref mut op) => { op.gate = op.gate.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Instruction(ref mut op) => { op.instruction = op.instruction.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } OperationType::Operation(ref mut op) => { op.operation = op.operation.call_method0(py, intern!(py, "copy"))?; - #[cfg(feature = "cache_pygates")] - { - inst.py_op = None; - } } }; + #[cfg(feature = "cache_pygates")] + { + *inst.py_op.borrow_mut() = None; + } } } Ok(res) @@ -589,87 +566,10 @@ impl CircuitData { /// Args: /// func (Callable[[:class:`~.Operation`], None]): /// The callable to invoke. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn foreach_op(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter() { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - - let op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - func.call1((op,))?; - } - Ok(()) - } - - /// Invokes callable ``func`` with each instruction's operation. - /// - /// Args: - /// func (Callable[[:class:`~.Operation`], None]): - /// The callable to invoke. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn foreach_op(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for inst in self.data.iter_mut() { - let op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } - }; - func.call1((op,))?; + func.call1((inst.unpack_py_op(py)?,))?; } Ok(()) } @@ -680,88 +580,10 @@ impl CircuitData { /// Args: /// func (Callable[[int, :class:`~.Operation`], None]): /// The callable to invoke. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for (index, inst) in self.data.iter().enumerate() { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - - let op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - func.call1((index, op))?; - } - Ok(()) - } - - /// Invokes callable ``func`` with the positional index and operation - /// of each instruction. - /// - /// Args: - /// func (Callable[[int, :class:`~.Operation`], None]): - /// The callable to invoke. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn foreach_op_indexed(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for (index, inst) in self.data.iter_mut().enumerate() { - let op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } - }; - func.call1((index, op))?; + func.call1((index, inst.unpack_py_op(py)?))?; } Ok(()) } @@ -779,49 +601,23 @@ impl CircuitData { /// func (Callable[[:class:`~.Operation`], :class:`~.Operation`]): /// A callable used to map original operation to their /// replacements. - #[cfg(not(feature = "cache_pygates"))] #[pyo3(signature = (func))] pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter_mut() { - let old_op = match &inst.op { - OperationType::Standard(op) => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - if condition.is_some() { - operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )? - } else { - op.into_py(py) + let py_op = { + if let OperationType::Standard(op) = inst.op { + match inst.extra_attrs.as_deref() { + None + | Some(ExtraInstructionAttributes { + condition: None, .. + }) => op.into_py(py), + _ => inst.unpack_py_op(py)?, } + } else { + inst.unpack_py_op(py)? } - OperationType::Gate(op) => op.gate.clone_ref(py), - OperationType::Instruction(op) => op.instruction.clone_ref(py), - OperationType::Operation(op) => op.operation.clone_ref(py), }; - let result: OperationInput = func.call1((old_op,))?.extract()?; + let result: OperationInput = func.call1((py_op,))?.extract()?; match result { OperationInput::Standard(op) => { inst.op = OperationType::Standard(op); @@ -836,7 +632,7 @@ impl CircuitData { inst.op = OperationType::Operation(op); } OperationInput::Object(new_op) => { - let new_inst_details = convert_py_to_operation_type(py, new_op)?; + let new_inst_details = convert_py_to_operation_type(py, new_op.clone_ref(py))?; inst.op = new_inst_details.operation; inst.params = new_inst_details.params; if new_inst_details.label.is_some() @@ -851,103 +647,10 @@ impl CircuitData { condition: new_inst_details.condition, })) } - } - } - } - Ok(()) - } - - /// Invokes callable ``func`` with each instruction's operation, - /// replacing the operation with the result. - /// - /// .. note:: - /// - /// This is only to be used by map_vars() in quantumcircuit.py it - /// assumes that a full Python instruction will only be returned from - /// standard gates iff a condition is set. - /// - /// Args: - /// func (Callable[[:class:`~.Operation`], :class:`~.Operation`]): - /// A callable used to map original operation to their - /// replacements. - #[cfg(feature = "cache_pygates")] - #[pyo3(signature = (func))] - pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { - for inst in self.data.iter_mut() { - let old_op = match &inst.py_op { - Some(op) => op.clone_ref(py), - None => match &inst.op { - OperationType::Standard(op) => { - let label; - let duration; - let unit; - let condition; - match &inst.extra_attrs { - Some(extra_attrs) => { - label = &extra_attrs.label; - duration = &extra_attrs.duration; - unit = &extra_attrs.unit; - condition = &extra_attrs.condition; - } - None => { - label = &None; - duration = &None; - unit = &None; - condition = &None; - } - } - if condition.is_some() { - let new_op = operation_type_and_data_to_py( - py, - &inst.op, - &inst.params, - label, - duration, - unit, - condition, - )?; - inst.py_op = Some(new_op.clone_ref(py)); - new_op - } else { - op.into_py(py) - } - } - OperationType::Gate(op) => op.gate.clone_ref(py), - OperationType::Instruction(op) => op.instruction.clone_ref(py), - OperationType::Operation(op) => op.operation.clone_ref(py), - }, - }; - let result: OperationInput = func.call1((old_op,))?.extract()?; - match result { - OperationInput::Standard(op) => { - inst.op = OperationType::Standard(op); - } - OperationInput::Gate(op) => { - inst.op = OperationType::Gate(op); - } - OperationInput::Instruction(op) => { - inst.op = OperationType::Instruction(op); - } - OperationInput::Operation(op) => { - inst.op = OperationType::Operation(op); - } - OperationInput::Object(new_op) => { - let new_inst_details = convert_py_to_operation_type(py, new_op.clone_ref(py))?; - inst.op = new_inst_details.operation; - inst.params = new_inst_details.params; - if new_inst_details.label.is_some() - || new_inst_details.duration.is_some() - || new_inst_details.unit.is_some() - || new_inst_details.condition.is_some() + #[cfg(feature = "cache_pygates")] { - inst.extra_attrs = Some(Box::new(ExtraInstructionAttributes { - label: new_inst_details.label, - duration: new_inst_details.duration, - unit: new_inst_details.unit, - condition: new_inst_details.condition, - })) + *inst.py_op.borrow_mut() = Some(new_op); } - inst.py_op = Some(new_op); } } } @@ -1537,7 +1240,7 @@ impl CircuitData { params: inst.params.clone(), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), + py_op: RefCell::new(inst.py_op.clone()), }) } @@ -1557,7 +1260,7 @@ impl CircuitData { params: inst.params.clone(), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), + py_op: RefCell::new(inst.py_op.clone()), }) } } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 2bb90367082d..5179190d8aa2 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -10,6 +10,9 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +#[cfg(feature = "cache_pygates")] +use std::cell::RefCell; + use pyo3::basic::CompareOp; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -47,8 +50,61 @@ pub(crate) struct PackedInstruction { pub clbits_id: Index, pub params: SmallVec<[Param; 3]>, pub extra_attrs: Option>, + #[cfg(feature = "cache_pygates")] - pub py_op: Option, + /// This is hidden in a `RefCell` because, while that has additional memory-usage implications + /// while we're still building with the feature enabled, we intend to remove the feature in the + /// future, and hiding the cache within a `RefCell` lets us keep the cache transparently in our + /// interfaces, without needing various functions to unnecessarily take `&mut` references. + pub py_op: RefCell>, +} + +impl PackedInstruction { + /// Build a reference to the Python-space operation object (the `Gate`, etc) packed into this + /// instruction. This may construct the reference if the `PackedInstruction` is a standard + /// gate with no already stored operation. + /// + /// A standard-gate operation object returned by this function is disconnected from the + /// containing circuit; updates to its label, duration, unit and condition will not be + /// propagated back. + pub fn unpack_py_op(&self, py: Python) -> PyResult> { + #[cfg(feature = "cache_pygates")] + { + if let Some(cached_op) = self.py_op.borrow().as_ref() { + return Ok(cached_op.clone_ref(py)); + } + } + let (label, duration, unit, condition) = match self.extra_attrs.as_deref() { + Some(ExtraInstructionAttributes { + label, + duration, + unit, + condition, + }) => ( + label.as_deref(), + duration.as_ref(), + unit.as_deref(), + condition.as_ref(), + ), + None => (None, None, None, None), + }; + let out = operation_type_and_data_to_py( + py, + &self.op, + &self.params, + label, + duration, + unit, + condition, + )?; + #[cfg(feature = "cache_pygates")] + { + if let Ok(mut cell) = self.py_op.try_borrow_mut() { + cell.get_or_insert_with(|| out.clone_ref(py)); + } + } + Ok(out) + } } /// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and @@ -666,20 +722,20 @@ pub(crate) fn operation_type_to_py( let (label, duration, unit, condition) = match &circuit_inst.extra_attrs { None => (None, None, None, None), Some(extra_attrs) => ( - extra_attrs.label.clone(), - extra_attrs.duration.clone(), - extra_attrs.unit.clone(), - extra_attrs.condition.clone(), + extra_attrs.label.as_deref(), + extra_attrs.duration.as_ref(), + extra_attrs.unit.as_deref(), + extra_attrs.condition.as_ref(), ), }; operation_type_and_data_to_py( py, &circuit_inst.operation, &circuit_inst.params, - &label, - &duration, - &unit, - &condition, + label, + duration, + unit, + condition, ) } @@ -692,10 +748,10 @@ pub(crate) fn operation_type_and_data_to_py( py: Python, operation: &OperationType, params: &[Param], - label: &Option, - duration: &Option, - unit: &Option, - condition: &Option, + label: Option<&str>, + duration: Option<&PyObject>, + unit: Option<&str>, + condition: Option<&PyObject>, ) -> PyResult { match &operation { OperationType::Standard(op) => { From 91f0c70885e6b58d1168e43c330373197b1c19ee Mon Sep 17 00:00:00 2001 From: "Tiago R. Cunha" <155388148+cstiago@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:29:48 -0300 Subject: [PATCH 161/179] Fix type hint in SolovayKitaevDecomposition (#12627) The return type hint of `find_basic_approximation` method changes from `Gate` to `GateSequence` in `SolovayKitaevDecomposition` class, as implied by `self.basic_approximations`. With no remaining mentions of `Gate`, its corresponding import statement is removed. --- qiskit/synthesis/discrete_basis/solovay_kitaev.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index 2e5cfeafcecd..2c8df5bd1b6d 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -16,8 +16,6 @@ import numpy as np -from qiskit.circuit.gate import Gate - from .gate_sequence import GateSequence from .commutator_decompose import commutator_decompose from .generate_basis_approximations import generate_basic_approximations, _1q_gates, _1q_inverses @@ -157,14 +155,14 @@ def _recurse(self, sequence: GateSequence, n: int, check_input: bool = True) -> w_n1 = self._recurse(w_n, n - 1, check_input=check_input) return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1) - def find_basic_approximation(self, sequence: GateSequence) -> Gate: - """Finds gate in ``self._basic_approximations`` that best represents ``sequence``. + def find_basic_approximation(self, sequence: GateSequence) -> GateSequence: + """Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``. Args: - sequence: The gate to find the approximation to. + sequence: ``GateSequence`` to find the approximation to. Returns: - Gate in basic approximations that is closest to ``sequence``. + ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``. """ # TODO explore using a k-d tree here From 8752f900f090bb46bc1e2468d76f99a797b9182f Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 21 Jun 2024 08:18:28 -0400 Subject: [PATCH 162/179] Add remaining tests for `ParameterVector` and tweak its `repr` (#12597) --- qiskit/circuit/parametervector.py | 2 +- test/python/circuit/test_parameters.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py index 151e3e7fea73..7b32395e1430 100644 --- a/qiskit/circuit/parametervector.py +++ b/qiskit/circuit/parametervector.py @@ -87,7 +87,7 @@ def __str__(self): return f"{self.name}, {[str(item) for item in self.params]}" def __repr__(self): - return f"{self.__class__.__name__}(name={self.name}, length={len(self)})" + return f"{self.__class__.__name__}(name={repr(self.name)}, length={len(self)})" def resize(self, length): """Resize the parameter vector. If necessary, new elements are generated. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index f841f969c00c..f580416eccf5 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1398,6 +1398,21 @@ def test_parametervector_resize(self): self.assertEqual(element, vec[1]) self.assertListEqual([param.name for param in vec], _paramvec_names("x", 3)) + def test_parametervector_repr(self): + """Test the __repr__ method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(repr(vec), "ParameterVector(name='x', length=2)") + + def test_parametervector_str(self): + """Test the __str__ method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(str(vec), "x, ['x[0]', 'x[1]']") + + def test_parametervector_index(self): + """Test the index method of the parameter vector.""" + vec = ParameterVector("x", 2) + self.assertEqual(vec.index(vec[1]), 1) + def test_raise_if_sub_unknown_parameters(self): """Verify we raise if asked to sub a parameter not in self.""" x = Parameter("x") From 22c145aa0e978e6cdefd59d0ff43e371b2d6487b Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 21 Jun 2024 13:28:57 +0100 Subject: [PATCH 163/179] Fix `CircuitInstruction` legacy iterable typing (#12630) The legacy 3-tuple format of `CircuitInstruction` still exposes the object in the old tuple-like way of `(Operation, list[Qubit], list[Clbit])`, rather than the new-style attribute access using tuples for the qargs and cargs. This was inadvertantly changed when it moved to Rust. --- crates/circuit/src/circuit_instruction.rs | 12 ++++++++++-- test/python/circuit/test_circuit_data.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 5179190d8aa2..93e73ccbc42f 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -542,7 +542,11 @@ impl CircuitInstruction { Ok(PyTuple::new_bound( py, - [op, self.qubits.to_object(py), self.clbits.to_object(py)], + [ + op, + self.qubits.bind(py).to_list().into(), + self.clbits.bind(py).to_list().into(), + ], )) } @@ -558,7 +562,11 @@ impl CircuitInstruction { }; Ok(PyTuple::new_bound( py, - [op, self.qubits.to_object(py), self.clbits.to_object(py)], + [ + op, + self.qubits.bind(py).to_list().into(), + self.clbits.bind(py).to_list().into(), + ], )) } diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index 6fc6e8e72bd7..35ae27b2fcfb 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -403,6 +403,22 @@ class TestQuantumCircuitInstructionData(QiskitTestCase): # but are included as tests to maintain compatability with the previous # list interface of circuit.data. + def test_iteration_of_data_entry(self): + """Verify that the base types of the legacy tuple iteration are correct, since they're + different to attribute access.""" + qc = QuantumCircuit(3, 3) + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + qc.measure([0, 1, 2], [0, 1, 2]) + + def to_legacy(instruction): + return (instruction.operation, list(instruction.qubits), list(instruction.clbits)) + + expected = [to_legacy(instruction) for instruction in qc.data] + actual = [tuple(instruction) for instruction in qc.data] + self.assertEqual(actual, expected) + def test_getitem_by_insertion_order(self): """Verify one can get circuit.data items in insertion order.""" qr = QuantumRegister(2) From 87aa89c19387ef7ff4177ef5144f60119da392e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:25:59 +0200 Subject: [PATCH 164/179] Add Rust representation for `XXMinusYYGate` and `XXPlusYYGate` (#12606) * Add XXMinusYYGate and XXPlusYYGate, implement parameter multiplication function (naive approach). Co-authored by: Julien Gacon jul@zurich.ibm.com * * Format code * Use multiply_param in RZGate * Fix signs and indices --- crates/circuit/src/gate_matrix.rs | 46 +++++ crates/circuit/src/imports.rs | 14 +- crates/circuit/src/operations.rs | 167 ++++++++++++++---- .../library/standard_gates/xx_minus_yy.py | 3 + .../library/standard_gates/xx_plus_yy.py | 3 + 5 files changed, 194 insertions(+), 39 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index ad8c918e73bc..23ce94869227 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -212,6 +212,7 @@ pub static ISWAP_GATE: [[Complex64; 4]; 4] = [ ]; pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]]; + pub static SDG_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]]; @@ -219,6 +220,7 @@ pub static T_GATE: [[Complex64; 2]; 2] = [ [c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)], ]; + pub static TDG_GATE: [[Complex64; 2]; 2] = [ [c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], @@ -246,3 +248,47 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos], ] } + +#[inline] +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [ + c64(cos, 0.), + c64(0., 0.), + c64(0., 0.), + c64(0., -sin) * c64(0., -beta).exp(), + ], + [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], + [ + c64(0., -sin) * c64(0., beta).exp(), + c64(0., 0.), + c64(0., 0.), + c64(cos, 0.), + ], + ] +} + +#[inline] +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [ + c64(0., 0.), + c64(cos, 0.), + c64(0., -sin) * c64(0., -beta).exp(), + c64(0., 0.), + ], + [ + c64(0., 0.), + c64(0., -sin) * c64(0., beta).exp(), + c64(cos, 0.), + c64(0., 0.), + ], + [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + ] +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 8db3b88fd7d2..3a9a942db8df 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -124,13 +124,23 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // SdgGate = 19 ["qiskit.circuit.library.standard_gates.s", "SdgGate"], // TGate = 20 - ["qiskit.circuit.library.standard_gates.s", "TGate"], + ["qiskit.circuit.library.standard_gates.t", "TGate"], // TdgGate = 21 - ["qiskit.circuit.library.standard_gates.s", "TdgGate"], + ["qiskit.circuit.library.standard_gates.t", "TdgGate"], // SXdgGate = 22 ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], // iSWAPGate = 23 ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], + //XXMinusYYGate = 24 + [ + "qiskit.circuit.library.standard_gates.xx_minus_yy", + "XXMinusYYGate", + ], + //XXPlusYYGate = 25 + [ + "qiskit.circuit.library.standard_gates.xx_plus_yy", + "XXPlusYYGate", + ], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 9048c55d9d48..6dedd3ac2061 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -203,14 +203,16 @@ pub enum StandardGate { TdgGate = 21, SXdgGate = 22, ISwapGate = 23, + XXMinusYYGate = 24, + XXPlusYYGate = 25, } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -238,6 +240,8 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "tdg", "sxdg", "iswap", + "xx_minus_yy", + "xx_plus_yy", ]; #[pymethods] @@ -287,7 +291,7 @@ impl StandardGate { // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 24; +pub const STANDARD_GATE_SIZE: usize = 26; impl Operation for StandardGate { fn name(&self) -> &str { @@ -416,6 +420,18 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), _ => None, }, + Self::XXMinusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::XXPlusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, } } @@ -502,6 +518,7 @@ impl Operation for StandardGate { }), Self::CXGate => None, Self::CCXGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; let q2 = smallvec![Qubit(2)]; let q0_1 = smallvec![Qubit(0), Qubit(1)]; @@ -524,7 +541,7 @@ impl Operation for StandardGate { (Self::TGate, smallvec![], q2.clone()), (Self::HGate, smallvec![], q2), (Self::CXGate, smallvec![], q0_1.clone()), - (Self::TGate, smallvec![], smallvec![Qubit(0)]), + (Self::TGate, smallvec![], q0), (Self::TdgGate, smallvec![], q1), (Self::CXGate, smallvec![], q0_1), ], @@ -536,39 +553,20 @@ impl Operation for StandardGate { Self::RXGate => todo!("Add when we have R"), Self::RYGate => todo!("Add when we have R"), Self::RZGate => Python::with_gil(|py| -> Option { - match ¶ms[0] { - Param::Float(theta) => Some( - CircuitData::from_standard_gates( - py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(*theta)], - smallvec![Qubit(0)], - )], - Param::Float(-0.5 * theta), - ) - .expect("Unexpected Qiskit python bug"), - ), - Param::ParameterExpression(theta) => Some( - CircuitData::from_standard_gates( - py, - 1, - [( - Self::PhaseGate, - smallvec![Param::ParameterExpression(theta.clone_ref(py))], - smallvec![Qubit(0)], - )], - Param::ParameterExpression( - theta - .call_method1(py, intern!(py, "__rmul__"), (-0.5,)) - .expect("Parameter expression for global phase failed"), - ), - ) - .expect("Unexpected Qiskit python bug"), - ), - Param::Obj(_) => unreachable!(), - } + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + smallvec![theta.clone()], + smallvec![Qubit(0)], + )], + multiply_param(theta, -0.5, py), + ) + .expect("Unexpected Qiskit python bug"), + ) }), Self::ECRGate => todo!("Add when we have RZX"), Self::SwapGate => Python::with_gil(|py| -> Option { @@ -732,6 +730,88 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZGate, + smallvec![multiply_param(beta, -1.0, py)], + q1.clone(), + ), + (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::SXGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q0.clone()), + (Self::SGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, 0.5, py)], + q0.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::SdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::SXdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q0), + (Self::RZGate, smallvec![beta.clone()], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q1_0 = smallvec![Qubit(1), Qubit(0)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RZGate, smallvec![beta.clone()], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::SXGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q1.clone()), + (Self::SGate, smallvec![], q0.clone()), + (Self::CXGate, smallvec![], q1_0.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q0.clone(), + ), + (Self::CXGate, smallvec![], q1_0), + (Self::SdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::SXdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI2)], q1), + (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), } } @@ -742,6 +822,19 @@ impl Operation for StandardGate { const FLOAT_ZERO: Param = Param::Float(0.0); +fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta * mult), + Param::ParameterExpression(theta) => Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__rmul__"), (mult,)) + .expect("Parameter expression for global phase failed"), + ), + Param::Obj(_) => unreachable!(), + } +} + /// This class is used to wrap a Python side Instruction that is not in the standard library #[derive(Clone, Debug)] #[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")] diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py index 4bf4ab80eca2..db3c3dc89153 100644 --- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py @@ -27,6 +27,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class XXMinusYYGate(Gate): @@ -91,6 +92,8 @@ class XXMinusYYGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.XXMinusYYGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index a82316ed7b09..7920454d0b98 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -21,6 +21,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.parameterexpression import ParameterValueType +from qiskit._accelerate.circuit import StandardGate class XXPlusYYGate(Gate): @@ -87,6 +88,8 @@ class XXPlusYYGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.XXPlusYYGate + def __init__( self, theta: ParameterValueType, From de6c6eb2f944c58ecf2258801e921e0846d41d89 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Mon, 24 Jun 2024 10:02:31 +0200 Subject: [PATCH 165/179] Follow up on #12327: circuit construction in Rust (#12605) * Follow up on #12327 also port circuit construction to rust and add a reno * move _get_ordered_swap to Rust only * drop redundant Ok(expect()) * proper synthesis structure --- crates/accelerate/src/lib.rs | 2 +- crates/accelerate/src/synthesis/mod.rs | 22 ++++++ .../src/synthesis/permutation/mod.rs | 68 +++++++++++++++++++ .../permutation/utils.rs} | 58 ++++------------ crates/pyext/src/lib.rs | 10 +-- qiskit/__init__.py | 2 +- .../library/generalized_gates/permutation.py | 17 +++-- .../synthesis/permutation/permutation_full.py | 14 +--- .../permutation/permutation_utils.py | 3 +- .../oxidize-permbasic-be27578187ac472f.yaml | 4 ++ .../synthesis/test_permutation_synthesis.py | 14 ---- 11 files changed, 124 insertions(+), 90 deletions(-) create mode 100644 crates/accelerate/src/synthesis/mod.rs create mode 100644 crates/accelerate/src/synthesis/permutation/mod.rs rename crates/accelerate/src/{permutation.rs => synthesis/permutation/utils.rs} (66%) create mode 100644 releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 3924c1de4092..dcfbdc9f1878 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -23,12 +23,12 @@ pub mod isometry; pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; -pub mod permutation; pub mod results; pub mod sabre; pub mod sampled_exp_val; pub mod sparse_pauli_op; pub mod stochastic_swap; +pub mod synthesis; pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs new file mode 100644 index 000000000000..f1a720459211 --- /dev/null +++ b/crates/accelerate/src/synthesis/mod.rs @@ -0,0 +1,22 @@ +// 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. + +mod permutation; + +use pyo3::prelude::*; +use pyo3::wrap_pymodule; + +#[pymodule] +pub fn synthesis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pymodule!(permutation::permutation))?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs new file mode 100644 index 000000000000..bf0ff97848f2 --- /dev/null +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -0,0 +1,68 @@ +// 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 numpy::PyArrayLike1; +use smallvec::smallvec; + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; + +mod utils; + +/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + utils::validate_permutation(&view)?; + Ok(py.None()) +} + +/// Finds inverse of a permutation pattern. +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let inverse_i64: Vec = utils::invert(&view).iter().map(|&x| x as i64).collect(); + Ok(inverse_i64.to_object(py)) +} + +#[pyfunction] +#[pyo3(signature = (pattern))] +pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyResult { + let view = pattern.as_array(); + let num_qubits = view.len(); + CircuitData::from_standard_gates( + py, + num_qubits as u32, + utils::get_ordered_swap(&view).iter().map(|(i, j)| { + ( + StandardGate::SwapGate, + smallvec![], + smallvec![Qubit(*i as u32), Qubit(*j as u32)], + ) + }), + Param::Float(0.0), + ) +} + +#[pymodule] +pub fn permutation(m: &Bound) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; + m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; + m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?; + Ok(()) +} diff --git a/crates/accelerate/src/permutation.rs b/crates/accelerate/src/synthesis/permutation/utils.rs similarity index 66% rename from crates/accelerate/src/permutation.rs rename to crates/accelerate/src/synthesis/permutation/utils.rs index 31ba433ddd30..a78088bfbfa9 100644 --- a/crates/accelerate/src/permutation.rs +++ b/crates/accelerate/src/synthesis/permutation/utils.rs @@ -11,12 +11,11 @@ // that they have been altered from the originals. use ndarray::{Array1, ArrayView1}; -use numpy::PyArrayLike1; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use std::vec::Vec; -fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { +pub fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { let n = pattern.len(); let mut seen: Vec = vec![false; n]; @@ -47,7 +46,7 @@ fn validate_permutation(pattern: &ArrayView1) -> PyResult<()> { Ok(()) } -fn invert(pattern: &ArrayView1) -> Array1 { +pub fn invert(pattern: &ArrayView1) -> Array1 { let mut inverse: Array1 = Array1::zeros(pattern.len()); pattern.iter().enumerate().for_each(|(ii, &jj)| { inverse[jj as usize] = ii; @@ -55,7 +54,16 @@ fn invert(pattern: &ArrayView1) -> Array1 { inverse } -fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { +/// Sorts the input permutation by iterating through the permutation list +/// and putting each element to its correct position via a SWAP (if it's not +/// at the correct position already). If ``n`` is the length of the input +/// permutation, this requires at most ``n`` SWAPs. +/// +/// More precisely, if the input permutation is a cycle of length ``m``, +/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); +/// if the input permutation consists of several disjoint cycles, then each cycle +/// is essentially treated independently. +pub fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { let mut permutation: Vec = pattern.iter().map(|&x| x as usize).collect(); let mut index_map = invert(pattern); @@ -76,45 +84,3 @@ fn get_ordered_swap(pattern: &ArrayView1) -> Vec<(i64, i64)> { swaps[..].reverse(); swaps } - -/// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - validate_permutation(&view)?; - Ok(py.None()) -} - -/// Finds inverse of a permutation pattern. -#[pyfunction] -#[pyo3(signature = (pattern))] -fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { - let view = pattern.as_array(); - let inverse_i64: Vec = invert(&view).iter().map(|&x| x as i64).collect(); - Ok(inverse_i64.to_object(py)) -} - -/// Sorts the input permutation by iterating through the permutation list -/// and putting each element to its correct position via a SWAP (if it's not -/// at the correct position already). If ``n`` is the length of the input -/// permutation, this requires at most ``n`` SWAPs. -/// -/// More precisely, if the input permutation is a cycle of length ``m``, -/// then this creates a quantum circuit with ``m-1`` SWAPs (and of depth ``m-1``); -/// if the input permutation consists of several disjoint cycles, then each cycle -/// is essentially treated independently. -#[pyfunction] -#[pyo3(signature = (permutation_in))] -fn _get_ordered_swap(py: Python, permutation_in: PyArrayLike1) -> PyResult { - let view = permutation_in.as_array(); - Ok(get_ordered_swap(&view).to_object(py)) -} - -#[pymodule] -pub fn permutation(m: &Bound) -> PyResult<()> { - m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; - m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; - m.add_function(wrap_pyfunction!(_get_ordered_swap, m)?)?; - Ok(()) -} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index b80aad1a7a45..72f0d759099a 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -17,10 +17,10 @@ use qiskit_accelerate::{ convert_2q_block_matrix::convert_2q_block_matrix, dense_layout::dense_layout, error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, - pauli_exp_val::pauli_expval, permutation::permutation, results::results, sabre::sabre, - sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, - stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, - utils::utils, vf2_layout::vf2_layout, + pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, + sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, synthesis::synthesis, + two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, + vf2_layout::vf2_layout, }; #[pymodule] @@ -36,7 +36,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(nlayout))?; m.add_wrapped(wrap_pymodule!(optimize_1q_gates))?; m.add_wrapped(wrap_pymodule!(pauli_expval))?; - m.add_wrapped(wrap_pymodule!(permutation))?; + m.add_wrapped(wrap_pymodule!(synthesis))?; m.add_wrapped(wrap_pymodule!(results))?; m.add_wrapped(wrap_pymodule!(sabre))?; m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index fce544333478..5b8505654428 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -80,7 +80,7 @@ sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout -sys.modules["qiskit._accelerate.permutation"] = _accelerate.permutation +sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py index 776c69d94f01..b2d17d2bed23 100644 --- a/qiskit/circuit/library/generalized_gates/permutation.py +++ b/qiskit/circuit/library/generalized_gates/permutation.py @@ -80,15 +80,13 @@ def __init__( name = "permutation_" + np.array_str(pattern).replace(" ", ",") - circuit = QuantumCircuit(num_qubits, name=name) - super().__init__(num_qubits, name=name) # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic - for i, j in _get_ordered_swap(pattern): - circuit.swap(i, j) + circuit = synth_permutation_basic(pattern) + circuit.name = name all_qubits = self.qubits self.append(circuit.to_gate(), all_qubits) @@ -184,10 +182,11 @@ def inverse(self, annotated: bool = False): def _qasm2_decomposition(self): # pylint: disable=cyclic-import - from qiskit.synthesis.permutation.permutation_utils import _get_ordered_swap + from qiskit.synthesis.permutation import synth_permutation_basic name = f"permutation__{'_'.join(str(n) for n in self.pattern)}_" - out = QuantumCircuit(self.num_qubits, name=name) - for i, j in _get_ordered_swap(self.pattern): - out.swap(i, j) + + out = synth_permutation_basic(self.pattern) + out.name = name + return out.to_gate() diff --git a/qiskit/synthesis/permutation/permutation_full.py b/qiskit/synthesis/permutation/permutation_full.py index ff014cb3a051..c280065c2a57 100644 --- a/qiskit/synthesis/permutation/permutation_full.py +++ b/qiskit/synthesis/permutation/permutation_full.py @@ -16,8 +16,8 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit._accelerate.synthesis.permutation import _synth_permutation_basic from .permutation_utils import ( - _get_ordered_swap, _inverse_pattern, _pattern_to_cycles, _decompose_cycles, @@ -44,17 +44,7 @@ def synth_permutation_basic(pattern: list[int] | np.ndarray[int]) -> QuantumCirc Returns: The synthesized quantum circuit. """ - # This is the very original Qiskit algorithm for synthesizing permutations. - - num_qubits = len(pattern) - qc = QuantumCircuit(num_qubits) - - swaps = _get_ordered_swap(pattern) - - for swap in swaps: - qc.swap(swap[0], swap[1]) - - return qc + return QuantumCircuit._from_circuit_data(_synth_permutation_basic(pattern)) def synth_permutation_acg(pattern: list[int] | np.ndarray[int]) -> QuantumCircuit: diff --git a/qiskit/synthesis/permutation/permutation_utils.py b/qiskit/synthesis/permutation/permutation_utils.py index dbd73bfe8111..4520e18f4d06 100644 --- a/qiskit/synthesis/permutation/permutation_utils.py +++ b/qiskit/synthesis/permutation/permutation_utils.py @@ -13,9 +13,8 @@ """Utility functions for handling permutations.""" # pylint: disable=unused-import -from qiskit._accelerate.permutation import ( +from qiskit._accelerate.synthesis.permutation import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) diff --git a/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml new file mode 100644 index 000000000000..e770aa1ca31b --- /dev/null +++ b/releasenotes/notes/oxidize-permbasic-be27578187ac472f.yaml @@ -0,0 +1,4 @@ +--- +upgrade_synthesis: + - | + Port :func:`.synth_permutation_basic`, used to synthesize qubit permutations, to Rust. diff --git a/test/python/synthesis/test_permutation_synthesis.py b/test/python/synthesis/test_permutation_synthesis.py index 050df5a3fe1c..b6a1ca9e1857 100644 --- a/test/python/synthesis/test_permutation_synthesis.py +++ b/test/python/synthesis/test_permutation_synthesis.py @@ -27,7 +27,6 @@ ) from qiskit.synthesis.permutation.permutation_utils import ( _inverse_pattern, - _get_ordered_swap, _validate_permutation, ) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -47,19 +46,6 @@ def test_inverse_pattern(self, width): for ii, jj in enumerate(pattern): self.assertTrue(inverse[jj] == ii) - @data(4, 5, 10, 15, 20) - def test_get_ordered_swap(self, width): - """Test _get_ordered_swap function produces correct swap list.""" - np.random.seed(1) - for _ in range(5): - pattern = np.random.permutation(width) - swap_list = _get_ordered_swap(pattern) - output = list(range(width)) - for i, j in swap_list: - output[i], output[j] = output[j], output[i] - self.assertTrue(np.array_equal(pattern, output)) - self.assertLess(len(swap_list), width) - @data(10, 20) def test_invalid_permutations(self, width): """Check that _validate_permutation raises exceptions when the From bf8f398fa4ddf287c6182b39bd27b324ab11dda0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 24 Jun 2024 10:05:06 -0400 Subject: [PATCH 166/179] Add rust representation for the u1, u2, and u3 gates (#12572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add rust representation for the u1, u2, and u3 gates This commit adds the rust representation of the U1Gate, U2Gate, and U3Gate to the `StandardGates` enum in rust. Part of #12566 * Update crates/circuit/src/imports.rs Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Fix test failures * Fix pylint pedantry --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 32 +++++ crates/circuit/src/imports.rs | 6 + crates/circuit/src/operations.rs | 74 +++++++++++- qiskit/circuit/library/standard_gates/u1.py | 3 + qiskit/circuit/library/standard_gates/u2.py | 3 + qiskit/circuit/library/standard_gates/u3.py | 3 + test/python/circuit/test_rust_equivalence.py | 25 ++-- test/python/qasm3/test_export.py | 114 +++++++++--------- .../transpiler/test_optimize_1q_gates.py | 36 +++++- 9 files changed, 224 insertions(+), 72 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 23ce94869227..2e5f55d6ddcb 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -271,6 +271,38 @@ pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { ] } +#[inline] +pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] { + [ + [c64(1., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., lam).exp()], + ] +} + +#[inline] +pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + [ + [ + c64(FRAC_1_SQRT_2, 0.), + (-c64(0., lam).exp()) * FRAC_1_SQRT_2, + ], + [ + c64(0., phi).exp() * FRAC_1_SQRT_2, + c64(0., phi + lam).exp() * FRAC_1_SQRT_2, + ], + ] +} + +#[inline] +pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { + let cos = (theta / 2.).cos(); + let sin = (theta / 2.).sin(); + [ + [c64(cos, 0.), -(c64(0., lam).exp()) * sin], + [c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos], + ] +} + #[inline] pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { let cos = (theta / 2.).cos(); diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 3a9a942db8df..7160798f56bb 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -141,6 +141,12 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ "qiskit.circuit.library.standard_gates.xx_plus_yy", "XXPlusYYGate", ], + // U1Gate = 26 + ["qiskit.circuit.library.standard_gates.u1", "U1Gate"], + // U2Gate = 27 + ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], + // U3Gate = 28 + ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 6dedd3ac2061..451b04947388 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -205,14 +205,17 @@ pub enum StandardGate { ISwapGate = 23, XXMinusYYGate = 24, XXPlusYYGate = 25, + U1Gate = 26, + U2Gate = 27, + U3Gate = 28, } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 3, ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ @@ -242,6 +245,9 @@ static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ "iswap", "xx_minus_yy", "xx_plus_yy", + "u1", + "u2", + "u3", ]; #[pymethods] @@ -290,8 +296,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) - -pub const STANDARD_GATE_SIZE: usize = 26; +pub const STANDARD_GATE_SIZE: usize = 29; impl Operation for StandardGate { fn name(&self) -> &str { @@ -432,6 +437,22 @@ impl Operation for StandardGate { } _ => None, }, + Self::U1Gate => match params[0] { + Param::Float(val) => Some(aview2(&gate_matrix::u1_gate(val)).to_owned()), + _ => None, + }, + Self::U2Gate => match params { + [Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u2_gate(*phi, *lam)).to_owned()) + } + _ => None, + }, + Self::U3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u3_gate(*theta, *phi, *lam)).to_owned()) + } + _ => None, + }, } } @@ -667,6 +688,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U1Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::PhaseGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::SdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -682,6 +718,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U2Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::UGate, + smallvec![Param::Float(PI / 2.), params[0].clone(), params[1].clone()], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -697,6 +748,21 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::U3Gate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 1, + [( + Self::UGate, + params.iter().cloned().collect(), + smallvec![Qubit(0)], + )], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::TdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 1d59cabae1f6..f141146b72dc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -19,6 +19,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import _ctrl_state_to_int +from qiskit._accelerate.circuit import StandardGate class U1Gate(Gate): @@ -92,6 +93,8 @@ class U1Gate(Gate): `1612.00858 `_ """ + _standard_gate = StandardGate.U1Gate + def __init__( self, theta: ParameterValueType, label: str | None = None, *, duration=None, unit="dt" ): diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index c8e4de96efec..9e59cd4c5bbd 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -18,6 +18,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U2Gate(Gate): @@ -86,6 +87,8 @@ class U2Gate(Gate): using two X90 pulses. """ + _standard_gate = StandardGate.U2Gate + def __init__( self, phi: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 0eef2518a85a..f191609ea8f1 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -19,6 +19,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.quantumregister import QuantumRegister +from qiskit._accelerate.circuit import StandardGate class U3Gate(Gate): @@ -80,6 +81,8 @@ class U3Gate(Gate): U3(\theta, 0, 0) = RY(\theta) """ + _standard_gate = StandardGate.U3Gate + def __init__( self, theta: ParameterValueType, diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index bb09ae4caf3f..8d6d159c0b64 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -79,12 +79,23 @@ def test_definitions(self): ) # Rust uses P but python still uses u1 elif rs_inst.operation.name == "p": - self.assertEqual(py_inst.operation.name, "u1") - self.assertEqual(rs_inst.operation.params, py_inst.operation.params) - self.assertEqual( - [py_def.find_bit(x).index for x in py_inst.qubits], - [rs_def.find_bit(x).index for x in rs_inst.qubits], - ) + if py_inst.operation.name == "u1": + self.assertEqual(py_inst.operation.name, "u1") + self.assertEqual(rs_inst.operation.params, py_inst.operation.params) + self.assertEqual( + [py_def.find_bit(x).index for x in py_inst.qubits], + [rs_def.find_bit(x).index for x in rs_inst.qubits], + ) + else: + self.assertEqual(py_inst.operation.name, "u3") + self.assertEqual( + rs_inst.operation.params[0], py_inst.operation.params[2] + ) + self.assertEqual( + [py_def.find_bit(x).index for x in py_inst.qubits], + [rs_def.find_bit(x).index for x in rs_inst.qubits], + ) + else: self.assertEqual(py_inst.operation.name, rs_inst.operation.name) self.assertEqual(rs_inst.operation.params, py_inst.operation.params) @@ -102,7 +113,7 @@ def test_matrix(self): continue with self.subTest(name=name): - params = [pi] * standard_gate._num_params() + params = [0.1] * standard_gate._num_params() py_def = gate_class.base_class(*params).to_matrix() rs_def = standard_gate._to_matrix(params) np.testing.assert_allclose(rs_def, py_def) diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 048c5d7852b4..6df041420882 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -2018,65 +2018,63 @@ def test_teleportation(self): qc.z(2).c_if(qc.clbits[0], 1) transpiled = transpile(qc, initial_layout=[0, 1, 2]) - first_h = transpiled.data[0].operation - u2 = first_h.definition.data[0].operation - u3_1 = u2.definition.data[0].operation - first_x = transpiled.data[-2].operation - u3_2 = first_x.definition.data[0].operation - first_z = transpiled.data[-1].operation - u1 = first_z.definition.data[0].operation - u3_3 = u1.definition.data[0].operation + id_len = len(str(id(transpiled.data[0].operation))) - expected_qasm = "\n".join( - [ - "OPENQASM 3.0;", - f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi/2, 0, pi) _gate_q_0;", - "}", - f"gate u2_{id(u2)}(_gate_p_0, _gate_p_1) _gate_q_0 {{", - f" u3_{id(u3_1)}(pi/2, 0, pi) _gate_q_0;", - "}", - "gate h _gate_q_0 {", - f" u2_{id(u2)}(0, pi) _gate_q_0;", - "}", - "gate cx c, t {", - " ctrl @ U(pi, 0, pi) c, t;", - "}", - f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(pi, 0, pi) _gate_q_0;", - "}", - "gate x _gate_q_0 {", - f" u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;", - "}", - f"gate u3_{id(u3_3)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{", - " U(0, 0, pi) _gate_q_0;", - "}", - f"gate u1_{id(u1)}(_gate_p_0) _gate_q_0 {{", - f" u3_{id(u3_3)}(0, 0, pi) _gate_q_0;", - "}", - "gate z _gate_q_0 {", - f" u1_{id(u1)}(pi) _gate_q_0;", - "}", - "bit[2] c;", - "h $1;", - "cx $1, $2;", - "barrier $0, $1, $2;", - "cx $0, $1;", - "h $0;", - "barrier $0, $1, $2;", - "c[0] = measure $0;", - "c[1] = measure $1;", - "barrier $0, $1, $2;", - "if (c[1]) {", - " x $2;", - "}", - "if (c[0]) {", - " z $2;", - "}", - "", - ] - ) - self.assertEqual(Exporter(includes=[]).dumps(transpiled), expected_qasm) + expected_qasm = [ + "OPENQASM 3.0;", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi/2, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate h _gate_q_0 {", + re.compile(r" u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len), + "}", + "gate cx c, t {", + " ctrl @ U(pi, 0, pi) c, t;", + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(pi, 0, pi) _gate_q_0;", + "}", + "gate x _gate_q_0 {", + re.compile(r" u3_\d{%s}\(pi, 0, pi\) _gate_q_0;" % id_len), + "}", + re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len), + " U(0, 0, pi) _gate_q_0;", + "}", + re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len), + re.compile(r" u3_\d{%s}\(0, 0, pi\) _gate_q_0;" % id_len), + "}", + "gate z _gate_q_0 {", + re.compile(r" u1_\d{%s}\(pi\) _gate_q_0;" % id_len), + "}", + "bit[2] c;", + "h $1;", + "cx $1, $2;", + "barrier $0, $1, $2;", + "cx $0, $1;", + "h $0;", + "barrier $0, $1, $2;", + "c[0] = measure $0;", + "c[1] = measure $1;", + "barrier $0, $1, $2;", + "if (c[1]) {", + " x $2;", + "}", + "if (c[0]) {", + " z $2;", + "}", + "", + ] + res = Exporter(includes=[]).dumps(transpiled).splitlines() + for result, expected in zip(res, expected_qasm): + if isinstance(expected, str): + self.assertEqual(result, expected) + else: + self.assertTrue( + expected.search(result), f"Line {result} doesn't match regex: {expected}" + ) def test_custom_gate_with_params_bound_main_call(self): """Custom gate with unbound parameters that are bound in the main circuit""" diff --git a/test/python/transpiler/test_optimize_1q_gates.py b/test/python/transpiler/test_optimize_1q_gates.py index 9253130bedbf..e5483dd47499 100644 --- a/test/python/transpiler/test_optimize_1q_gates.py +++ b/test/python/transpiler/test_optimize_1q_gates.py @@ -19,7 +19,7 @@ from qiskit.transpiler import PassManager from qiskit.transpiler.passes import Optimize1qGates, BasisTranslator from qiskit.converters import circuit_to_dag -from qiskit.circuit import Parameter +from qiskit.circuit import Parameter, Gate from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, UGate, PhaseGate from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target @@ -323,9 +323,24 @@ def test_parameterized_expressions_in_circuits(self): def test_global_phase_u3_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate definition.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 @@ -337,9 +352,24 @@ def test_global_phase_u3_on_left(self): def test_global_phase_u_on_left(self): """Check proper phase accumulation with instruction with no definition.""" + + class CustomGate(Gate): + """Custom u1 gate.""" + + def __init__(self, lam): + super().__init__("u1", 1, [lam]) + + def _define(self): + qc = QuantumCircuit(1) + qc.p(*self.params, 0) + self.definition = qc + + def _matrix(self): + return U1Gate(*self.params).to_matrix() + qr = QuantumRegister(1) qc = QuantumCircuit(qr) - u1 = U1Gate(0.1) + u1 = CustomGate(0.1) u1.definition.global_phase = np.pi / 2 qc.append(u1, [0]) qc.global_phase = np.pi / 3 From 35f6297f20be6d9b58671948adc179dad77894af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:23:42 -0400 Subject: [PATCH 167/179] Bump faer from 0.19.0 to 0.19.1 (#12645) Bumps [faer](https://github.com/sarah-ek/faer-rs) from 0.19.0 to 0.19.1. - [Changelog](https://github.com/sarah-ek/faer-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/sarah-ek/faer-rs/commits) --- updated-dependencies: - dependency-name: faer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/accelerate/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aefa3c932a04..454823748e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "faer" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ef9e1a4098a9e3a03c47bc5061406c04820552d869fd0fcd92587d07b271f0" +checksum = "41543c4de4bfb32efdffdd75cbcca5ef41b800e8a811ea4a41fb9393c6ef3bc0" dependencies = [ "bytemuck", "coe-rs", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d9865d545437..b377a9b38a6d 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,7 +20,7 @@ num-traits = "0.2" num-complex.workspace = true num-bigint = "0.4" rustworkx-core = "0.14" -faer = "0.19.0" +faer = "0.19.1" itertools = "0.13.0" qiskit-circuit.workspace = true From b20a7ceb58b0ec7e49004d3ce81bd3f2144e6f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Mon, 24 Jun 2024 20:16:16 +0200 Subject: [PATCH 168/179] Add placeholders for all mising standard gates in Rust (#12646) * Add placeholders for all gates, mark TODOs * Update name for CPhase * Remove todo from Ux gates --- crates/circuit/src/imports.rs | 53 ++++++++++- crates/circuit/src/operations.rs | 156 ++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 34 deletions(-) diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 7160798f56bb..76e808d1b308 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -79,6 +79,7 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = /// /// NOTE: the order here is significant, the StandardGate variant's number must match /// index of it's entry in this table. This is all done statically for performance +// TODO: replace placeholders with actual implementation static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // ZGate = 0 ["qiskit.circuit.library.standard_gates.z", "ZGate"], @@ -131,12 +132,12 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], // iSWAPGate = 23 ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], - //XXMinusYYGate = 24 + // XXMinusYYGate = 24 [ "qiskit.circuit.library.standard_gates.xx_minus_yy", "XXMinusYYGate", ], - //XXPlusYYGate = 25 + // XXPlusYYGate = 25 [ "qiskit.circuit.library.standard_gates.xx_plus_yy", "XXPlusYYGate", @@ -147,6 +148,54 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], // U3Gate = 28 ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], + // CRXGate = 29 + ["placeholder", "placeholder"], + // CRYGate = 30 + ["placeholder", "placeholder"], + // CRZGate = 31 + ["placeholder", "placeholder"], + // RGate 32 + ["placeholder", "placeholder"], + // CHGate = 33 + ["qiskit.circuit.library.standard_gates.h", "CHGate"], + // CPhaseGate = 34 + ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], + // CSGate = 35 + ["qiskit.circuit.library.standard_gates.s", "CSGate"], + // CSdgGate = 36 + ["qiskit.circuit.library.standard_gates.s", "CSdgGate"], + // CSXGate = 37 + ["qiskit.circuit.library.standard_gates.sx", "CSXGate"], + // CSwapGate = 38 + ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], + // CUGate = 39 + ["qiskit.circuit.library.standard_gates.u", "CUGate"], + // CU1Gate = 40 + ["qiskit.circuit.library.standard_gates.u1", "CU1Gate"], + // CU3Gate = 41 + ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], + // C3XGate = 42 + ["placeholder", "placeholder"], + // C3SXGate = 43 + ["placeholder", "placeholder"], + // C4XGate = 44 + ["placeholder", "placeholder"], + // DCXGate = 45 + ["placeholder", "placeholder"], + // CCZGate = 46 + ["placeholder", "placeholder"], + // RCCXGate = 47 + ["placeholder", "placeholder"], + // RC3XGate = 48 + ["placeholder", "placeholder"], + // RXXGate = 49 + ["placeholder", "placeholder"], + // RYYGate = 50 + ["placeholder", "placeholder"], + // RZZGate = 51 + ["placeholder", "placeholder"], + // RZXGate = 52 + ["placeholder", "placeholder"], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 451b04947388..af7dabc86216 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -208,46 +208,106 @@ pub enum StandardGate { U1Gate = 26, U2Gate = 27, U3Gate = 28, + CRXGate = 29, + CRYGate = 30, + CRZGate = 31, + RGate = 32, + CHGate = 33, + CPhaseGate = 34, + CSGate = 35, + CSdgGate = 36, + CSXGate = 37, + CSwapGate = 38, + CUGate = 39, + CU1Gate = 40, + CU3Gate = 41, + C3XGate = 42, + C3SXGate = 43, + C4XGate = 44, + DCXGate = 45, + CCZGate = 46, + RCCXGate = 47, + RC3XGate = 48, + RXXGate = 49, + RYYGate = 50, + RZZGate = 51, + RZXGate = 52, } +// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9 + 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 + 1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29 + 34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39 + 2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 34, 34, 34, // 50-52 ]; +// TODO: replace all 34s (placeholders) with actual number static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 3, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9 + 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 + 0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29 + 34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39 + 1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 34, 34, 34, // 50-52 ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ - "z", - "y", - "x", - "cz", - "cy", - "cx", - "ccx", - "rx", - "ry", - "rz", - "ecr", - "swap", - "sx", - "global_phase", - "id", - "h", - "p", - "u", - "s", - "sdg", - "t", - "tdg", - "sxdg", - "iswap", - "xx_minus_yy", - "xx_plus_yy", - "u1", - "u2", - "u3", + "z", // 0 + "y", // 1 + "x", // 2 + "cz", // 3 + "cy", // 4 + "cx", // 5 + "ccx", // 6 + "rx", // 7 + "ry", // 8 + "rz", // 9 + "ecr", // 10 + "swap", // 11 + "sx", // 12 + "global_phase", // 13 + "id", // 14 + "h", // 15 + "p", // 16 + "u", // 17 + "s", // 18 + "sdg", // 19 + "t", // 20 + "tdg", // 21 + "sxdg", // 22 + "iswap", // 23 + "xx_minus_yy", // 24 + "xx_plus_yy", // 25 + "u1", // 26 + "u2", // 27 + "u3", // 28 + "crx", // 29 + "cry", // 30 + "crz", // 31 + "r", // 32 + "ch", // 33 + "cp", // 34 + "cs", // 35 + "csdg", // 36 + "csx", // 37 + "cswap", // 38 + "cu", // 39 + "cu1", // 40 + "cu3", // 41 + "c3x", // 42 + "c3sx", // 43 + "c4x", // 44 + "dcx", // 45 + "ccz", // 46 + "rccx", // 47 + "rc3x", // 48 + "rxx", // 49 + "ryy", // 50 + "rzz", // 51 + "rzx", // 52 ]; #[pymethods] @@ -296,7 +356,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 29; +pub const STANDARD_GATE_SIZE: usize = 53; impl Operation for StandardGate { fn name(&self) -> &str { @@ -453,6 +513,21 @@ impl Operation for StandardGate { } _ => None, }, + Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), + Self::RGate => todo!(), + Self::CHGate => todo!(), + Self::CPhaseGate => todo!(), + Self::CSGate => todo!(), + Self::CSdgGate => todo!(), + Self::CSXGate => todo!(), + Self::CSwapGate => todo!(), + Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), + Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), + Self::DCXGate => todo!(), + Self::CCZGate => todo!(), + Self::RCCXGate | Self::RC3XGate => todo!(), + Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), + Self::RZXGate => todo!(), } } @@ -878,6 +953,23 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), + Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), + Self::RGate => todo!(), + Self::CHGate => todo!(), + Self::CPhaseGate => todo!(), + Self::CSGate => todo!(), + Self::CSdgGate => todo!(), + Self::CSXGate => todo!(), + Self::CSwapGate => todo!(), + Self::CUGate => todo!(), + Self::CU1Gate => todo!(), + Self::CU3Gate => todo!(), + Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), + Self::DCXGate => todo!(), + Self::CCZGate => todo!(), + Self::RCCXGate | Self::RC3XGate => todo!(), + Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), + Self::RZXGate => todo!(), } } From 8b1f75ffafc70596bcf45480aa2f6d59d822e337 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 24 Jun 2024 19:21:20 +0100 Subject: [PATCH 169/179] Deprecate tuple-like access to `CircuitInstruction` (#12640) This has been the legacy path since `CircuitInstruction` was added in gh-8093. It's more performant to use the attribute-access patterns, and with more of the internals moving to Rust and potentially needing more use of additional class methods and attributes, we need to start shifting people away from the old form. --- crates/circuit/src/circuit_instruction.rs | 39 +++++++++++++++++-- crates/circuit/src/imports.rs | 2 + ...-circuit-instruction-8a332ab09de73766.yaml | 23 +++++++++++ test/python/circuit/test_circuit_data.py | 6 ++- test/utils/base.py | 9 +++++ 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 93e73ccbc42f..781a776c1566 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use pyo3::basic::CompareOp; -use pyo3::exceptions::PyValueError; +use pyo3::exceptions::{PyDeprecationWarning, PyValueError}; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyList, PyTuple, PyType}; use pyo3::{intern, IntoPy, PyObject, PyResult}; @@ -22,7 +22,7 @@ use smallvec::{smallvec, SmallVec}; use crate::imports::{ get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION, - SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, + SINGLETON_CONTROLLED_GATE, SINGLETON_GATE, WARNINGS_WARN, }; use crate::interner::Index; use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate}; @@ -572,26 +572,31 @@ impl CircuitInstruction { #[cfg(not(feature = "cache_pygates"))] pub fn __getitem__(&self, py: Python<'_>, key: &Bound) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py)) } #[cfg(feature = "cache_pygates")] pub fn __getitem__(&mut self, py: Python<'_>, key: &Bound) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py)) } #[cfg(not(feature = "cache_pygates"))] pub fn __iter__(&self, py: Python<'_>) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py)) } #[cfg(feature = "cache_pygates")] pub fn __iter__(&mut self, py: Python<'_>) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py)) } - pub fn __len__(&self) -> usize { - 3 + pub fn __len__(&self, py: Python) -> PyResult { + warn_on_legacy_circuit_instruction_iteration(py)?; + Ok(3) } pub fn __richcmp__( @@ -939,3 +944,29 @@ pub(crate) fn convert_py_to_operation_type( } Err(PyValueError::new_err(format!("Invalid input: {}", py_op))) } + +/// Issue a Python `DeprecationWarning` about using the legacy tuple-like interface to +/// `CircuitInstruction`. +/// +/// Beware the `stacklevel` here doesn't work quite the same way as it does in Python as Rust-space +/// calls are completely transparent to Python. +#[inline] +fn warn_on_legacy_circuit_instruction_iteration(py: Python) -> PyResult<()> { + WARNINGS_WARN + .get_bound(py) + .call1(( + intern!( + py, + concat!( + "Treating CircuitInstruction as an iterable is deprecated legacy behavior", + " since Qiskit 1.2, and will be removed in Qiskit 2.0.", + " Instead, use the `operation`, `qubits` and `clbits` named attributes." + ) + ), + py.get_type_bound::(), + // Stack level. Compared to Python-space calls to `warn`, this is unusually low + // beacuse all our internal call structure is now Rust-space and invisible to Python. + 1, + )) + .map(|_| ()) +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 76e808d1b308..92700f3274e7 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -72,6 +72,8 @@ pub static SINGLETON_GATE: ImportOnceCell = pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate"); +pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn"); + /// A mapping from the enum variant in crate::operations::StandardGate to the python /// module path and class name to import it. This is used to populate the conversion table /// when a gate is added directly via the StandardGate path and there isn't a Python object diff --git a/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml b/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml new file mode 100644 index 000000000000..d656ee5cb823 --- /dev/null +++ b/releasenotes/notes/deprecate-legacy-circuit-instruction-8a332ab09de73766.yaml @@ -0,0 +1,23 @@ +--- +deprecations_circuits: + - | + Treating :class:`.CircuitInstruction` as a tuple-like iterable is deprecated, and this legacy + path way will be removed in Qiskit 2.0. You should use the attribute-access fields + :attr:`~.CircuitInstruction.operation`, :attr:`~.CircuitInstruction.qubits`, and + :attr:`~.CircuitInstruction.clbits` instead. For example:: + + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(2, 2) + qc.h(0) + qc.cx(0, 1) + qc.measure([0, 1], [0, 1]) + + # Deprecated. + for op, qubits, clbits in qc.data: + pass + # New style. + for instruction in qc.data: + op = instruction.operation + qubits = instruction.qubits + clbits = instruction.clbits diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py index 35ae27b2fcfb..55028c8e883e 100644 --- a/test/python/circuit/test_circuit_data.py +++ b/test/python/circuit/test_circuit_data.py @@ -416,7 +416,11 @@ def to_legacy(instruction): return (instruction.operation, list(instruction.qubits), list(instruction.clbits)) expected = [to_legacy(instruction) for instruction in qc.data] - actual = [tuple(instruction) for instruction in qc.data] + + with self.assertWarnsRegex( + DeprecationWarning, "Treating CircuitInstruction as an iterable is deprecated" + ): + actual = [tuple(instruction) for instruction in qc.data] self.assertEqual(actual, expected) def test_getitem_by_insertion_order(self): diff --git a/test/utils/base.py b/test/utils/base.py index 63a8bf4384f0..bebf03008858 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -215,6 +215,15 @@ def setUpClass(cls): module=r"seaborn(\..*)?", ) + # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version + # of Aer. + warnings.filterwarnings( + "default", + category=DeprecationWarning, + message="Treating CircuitInstruction as an iterable is deprecated", + module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", + ) + allow_DeprecationWarning_modules = [ "test.python.pulse.test_builder", "test.python.pulse.test_block", From 1ed5951a98b594808525c8428e06178c160cfcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:53:54 +0200 Subject: [PATCH 170/179] Pin scipy to 1.13.1 to bypass CI failures (#12654) * Pin scipy to 1.13.1 to bypass CI failures * whoops * double whoops --- constraints.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/constraints.txt b/constraints.txt index d3985581d362..6681de226d93 100644 --- a/constraints.txt +++ b/constraints.txt @@ -3,6 +3,10 @@ # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. scipy<1.11; python_version<'3.12' +# Temporary pin to avoid CI issues caused by scipy 1.14.0 +# See https://github.com/Qiskit/qiskit/issues/12655 for current details. +scipy==1.13.1; python_version=='3.12' + # z3-solver from 4.12.3 onwards upped the minimum macOS API version for its # wheels to 11.7. The Azure VM images contain pre-built CPythons, of which at # least CPython 3.8 was compiled for an older macOS, so does not match a From 6974b4500f6c716b407b599e5cec80afbb757516 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Wed, 26 Jun 2024 08:29:34 +0200 Subject: [PATCH 171/179] Fix some bugs in loading Solovay Kitaev decompositions (#12579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix store & load - fix access via .item() - fix storing of global phase - fix storing ofgate sequence labels * undangle a dangling print * fix import order * Update releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- .../generate_basis_approximations.py | 2 +- .../discrete_basis/solovay_kitaev.py | 24 ++++++++++---- ...ix-sk-load-from-file-02c6eabbbd7fcda3.yaml | 10 ++++++ test/python/transpiler/test_solovay_kitaev.py | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml diff --git a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py index 672d0eb9e8ef..da9708c24559 100644 --- a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py +++ b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py @@ -156,7 +156,7 @@ def generate_basic_approximations( data = {} for sequence in sequences: gatestring = sequence.name - data[gatestring] = sequence.product + data[gatestring] = (sequence.product, sequence.global_phase) np.save(filename, data) diff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py index 2c8df5bd1b6d..f367f6c0f0b5 100644 --- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py +++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py @@ -51,14 +51,19 @@ def __init__( self.basic_approximations = self.load_basic_approximations(basic_approximations) - def load_basic_approximations(self, data: list | str | dict) -> list[GateSequence]: + @staticmethod + def load_basic_approximations(data: list | str | dict) -> list[GateSequence]: """Load basic approximations. Args: data: If a string, specifies the path to the file from where to load the data. - If a dictionary, directly specifies the decompositions as ``{gates: matrix}``. - There ``gates`` are the names of the gates producing the SO(3) matrix ``matrix``, - e.g. ``{"h t": np.array([[0, 0.7071, -0.7071], [0, -0.7071, -0.7071], [-1, 0, 0]]}``. + If a dictionary, directly specifies the decompositions as ``{gates: matrix}`` + or ``{gates: (matrix, global_phase)}``. There, ``gates`` are the names of the gates + producing the SO(3) matrix ``matrix``, e.g. + ``{"h t": np.array([[0, 0.7071, -0.7071], [0, -0.7071, -0.7071], [-1, 0, 0]]}`` + and the ``global_phase`` can be given to account for a global phase difference + between the U(2) matrix of the quantum gates and the stored SO(3) matrix. + If not given, the ``global_phase`` will be assumed to be 0. Returns: A list of basic approximations as type ``GateSequence``. @@ -72,13 +77,20 @@ def load_basic_approximations(self, data: list | str | dict) -> list[GateSequenc # if a file, load the dictionary if isinstance(data, str): - data = np.load(data, allow_pickle=True) + data = np.load(data, allow_pickle=True).item() sequences = [] - for gatestring, matrix in data.items(): + for gatestring, matrix_and_phase in data.items(): + if isinstance(matrix_and_phase, tuple): + matrix, global_phase = matrix_and_phase + else: + matrix, global_phase = matrix_and_phase, 0 + sequence = GateSequence() sequence.gates = [_1q_gates[element] for element in gatestring.split()] + sequence.labels = [gate.name for gate in sequence.gates] sequence.product = np.asarray(matrix) + sequence.global_phase = global_phase sequences.append(sequence) return sequences diff --git a/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml b/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml new file mode 100644 index 000000000000..d995af06bccb --- /dev/null +++ b/releasenotes/notes/fix-sk-load-from-file-02c6eabbbd7fcda3.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fix the :class:`.SolovayKitaev` transpiler pass when loading basic + approximations from an exising ``.npy`` file. Previously, loading + a stored approximation which allowed for further reductions (e.g. due + to gate cancellations) could cause a runtime failure. + Additionally, the global phase difference of the U(2) gate product + and SO(3) representation was lost during a save-reload procedure. + Fixes `Qiskit/qiskit#12576 `_. diff --git a/test/python/transpiler/test_solovay_kitaev.py b/test/python/transpiler/test_solovay_kitaev.py index e15a080f6f03..62b811c8e3bd 100644 --- a/test/python/transpiler/test_solovay_kitaev.py +++ b/test/python/transpiler/test_solovay_kitaev.py @@ -12,8 +12,10 @@ """Test the Solovay Kitaev transpilation pass.""" +import os import unittest import math +import tempfile import numpy as np import scipy @@ -230,6 +232,35 @@ def test_u_gates_work(self): included_gates = set(discretized.count_ops().keys()) self.assertEqual(set(basis_gates), included_gates) + def test_load_from_file(self): + """Test loading basic approximations from a file works. + + Regression test of Qiskit/qiskit#12576. + """ + filename = "approximations.npy" + + with tempfile.TemporaryDirectory() as tmp_dir: + fullpath = os.path.join(tmp_dir, filename) + + # dump approximations to file + generate_basic_approximations(basis_gates=["h", "s", "sdg"], depth=3, filename=fullpath) + + # circuit to decompose and reference decomp + circuit = QuantumCircuit(1) + circuit.rx(0.8, 0) + + reference = QuantumCircuit(1, global_phase=3 * np.pi / 4) + reference.h(0) + reference.s(0) + reference.h(0) + + # load the decomp and compare to reference + skd = SolovayKitaev(basic_approximations=fullpath) + # skd = SolovayKitaev(basic_approximations=filename) + discretized = skd(circuit) + + self.assertEqual(discretized, reference) + @ddt class TestGateSequence(QiskitTestCase): From e36027c01a5d18b72225502c0fd5021613893623 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 26 Jun 2024 09:41:11 +0200 Subject: [PATCH 172/179] GenericBackendV2 should fail when the backend cannot allocate the basis gate because its size (#12653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GenericBackendV2 should fail when the backend cannot allocate the basis gate because its size Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * reno * Update releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * another single qubit backend --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit/providers/fake_provider/generic_backend_v2.py | 5 +++++ .../notes/fixes_GenericBackendV2-668e40596e1f070d.yaml | 4 ++++ .../providers/fake_provider/test_generic_backend_v2.py | 10 ++++++++++ test/visual/mpl/graph/test_graph_matplotlib_drawer.py | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 1ac0484d775d..214754080e57 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -375,6 +375,11 @@ def _build_generic_target(self): f"in the standard qiskit circuit library." ) gate = self._supported_gates[name] + if self.num_qubits < gate.num_qubits: + raise QiskitError( + f"Provided basis gate {name} needs more qubits than {self.num_qubits}, " + f"which is the size of the backend." + ) noise_params = self._get_noise_defaults(name, gate.num_qubits) self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) diff --git a/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml b/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml new file mode 100644 index 000000000000..9d297125e3c2 --- /dev/null +++ b/releasenotes/notes/fixes_GenericBackendV2-668e40596e1f070d.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + The constructor :class:`.GenericBackendV2` was allowing to create malformed backends because it accepted basis gates that couldn't be allocated in the backend size . That is, a backend with a single qubit should not accept a basis with two-qubit gates. diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index b4fbe944c332..cd7c611b2212 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -35,6 +35,16 @@ def test_supported_basis_gates(self): with self.assertRaises(QiskitError): GenericBackendV2(num_qubits=8, basis_gates=["cx", "id", "rz", "sx", "zz"]) + def test_cx_1Q(self): + """Test failing with a backend with single qubit but with a two-qubit basis gate""" + with self.assertRaises(QiskitError): + GenericBackendV2(num_qubits=1, basis_gates=["cx", "id"]) + + def test_ccx_2Q(self): + """Test failing with a backend with two qubits but with a three-qubit basis gate""" + with self.assertRaises(QiskitError): + GenericBackendV2(num_qubits=2, basis_gates=["ccx", "id"]) + def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" diff --git a/test/visual/mpl/graph/test_graph_matplotlib_drawer.py b/test/visual/mpl/graph/test_graph_matplotlib_drawer.py index ae69f212f89c..20fae107d30d 100644 --- a/test/visual/mpl/graph/test_graph_matplotlib_drawer.py +++ b/test/visual/mpl/graph/test_graph_matplotlib_drawer.py @@ -389,7 +389,7 @@ def test_plot_1_qubit_gate_map(self): """Test plot_gate_map using 1 qubit backend""" # getting the mock backend from FakeProvider - backend = GenericBackendV2(num_qubits=1) + backend = GenericBackendV2(num_qubits=1, basis_gates=["id", "rz", "sx", "x"]) fname = "1_qubit_gate_map.png" self.graph_plot_gate_map(backend=backend, filename=fname) From 4d3821b01a70c40e8542646ad92759aae877a096 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 26 Jun 2024 08:06:23 -0400 Subject: [PATCH 173/179] Simplify QuantumCircuit._from_circuit_data bit handling (#12661) * Simplify QuantumCircuit._from_circuit_data bit handling This commit simplifies the logic around bit handling in the `QuantumCircuit._from_circuit_data()` constructor. Previously it was calling `add_bits()` for each bit in the `CircuitData` object to update the output circuit's accounting for each qubit. But this was needlessly heavy as the `CircuitData` is already the source of truth for the bits in a circuit and we just need to update the indices dictionary. The `add_bits()` method attempts to add the bits to the `CircuitData` too but this is wasted overhead because the `CircuitData` already has the bits as that's where the came from. This changes the constructor to just directly set the bit indices as needed and return the circuit. * Use a dict comprehension instead of a for loop --- qiskit/circuit/quantumcircuit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ee52e3308a94..8b3ff7bf1979 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1161,9 +1161,9 @@ def __init__( def _from_circuit_data(cls, data: CircuitData) -> typing.Self: """A private constructor from rust space circuit data.""" out = QuantumCircuit() - out.add_bits(data.qubits) - out.add_bits(data.clbits) out._data = data + out._qubit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.qubits)} + out._clbit_indices = {bit: BitLocations(index, []) for index, bit in enumerate(data.clbits)} return out @staticmethod From 26680dcecf9457210f46aa6b2f62361a5ccd8d84 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 26 Jun 2024 14:45:24 +0200 Subject: [PATCH 174/179] adapting test/randomized/test_transpiler_equivalence.py to #12640 (#12663) * addapting to #12640 * more instances --- test/randomized/test_transpiler_equivalence.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 04dced90dfaa..3bd09d89348b 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -258,9 +258,9 @@ def add_c_if_last_gate(self, carg, data): last_gate = self.qc.data[-1] # Conditional instructions are not supported - assume(isinstance(last_gate[0], Gate)) + assume(isinstance(last_gate.operation, Gate)) - last_gate[0].c_if(creg, val) + last_gate.operation.c_if(creg, val) # Properties to check @@ -269,7 +269,7 @@ def qasm(self): """After each circuit operation, it should be possible to build QASM.""" qasm2.dumps(self.qc) - @precondition(lambda self: any(isinstance(d[0], Measure) for d in self.qc.data)) + @precondition(lambda self: any(isinstance(d.operation, Measure) for d in self.qc.data)) @rule(kwargs=transpiler_conf()) def equivalent_transpile(self, kwargs): """Simulate, transpile and simulate the present circuit. Verify that the From 39b2c90b813da63d006755580cabd80b718b74bb Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 26 Jun 2024 09:48:25 -0400 Subject: [PATCH 175/179] Add test case to validate the rust->Python gate conversion (#12623) This commit adds a test to the test_rust_equivalence module to assert that the Python gate objects returned from the Rust CircuitData is the correct type. --- test/python/circuit/test_rust_equivalence.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 8d6d159c0b64..b20db4c79f94 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -18,7 +18,7 @@ import numpy as np -from qiskit.circuit import QuantumCircuit +from qiskit.circuit import QuantumCircuit, CircuitInstruction from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping SKIP_LIST = {"rx", "ry", "ecr"} @@ -39,6 +39,21 @@ def setUp(self): gate = gate.base_class(*[pi] * len(gate.params)) qc.append(gate, list(range(gate.num_qubits))) + def test_gate_cross_domain_conversion(self): + """Test the rust -> python conversion returns the right class.""" + for name, gate_class in self.standard_gates.items(): + standard_gate = getattr(gate_class, "_standard_gate", None) + if standard_gate is None: + # Gate not in rust yet or no constructor method + continue + with self.subTest(name=name): + qc = QuantumCircuit(standard_gate.num_qubits) + qc._append( + CircuitInstruction(standard_gate, qubits=qc.qubits, params=gate_class.params) + ) + self.assertEqual(qc.data[0].operation.base_class, gate_class.base_class) + self.assertEqual(qc.data[0].operation, gate_class) + def test_definitions(self): """Test definitions are the same in rust space.""" for name, gate_class in self.standard_gates.items(): From 2fab2007e3d7384e9e55a10340952c4974ece03c Mon Sep 17 00:00:00 2001 From: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:15:10 +0300 Subject: [PATCH 176/179] Add Rust representation for DCXGate (#12644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updating tables * Adding remaining code * Appending the Rust representation directly * Fix fmt --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 7 ++++++ crates/circuit/src/imports.rs | 2 +- crates/circuit/src/operations.rs | 25 ++++++++++++++++---- qiskit/circuit/library/standard_gates/dcx.py | 3 +++ qiskit/circuit/quantumcircuit.py | 4 +--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2e5f55d6ddcb..80fecfb597c6 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -226,6 +226,13 @@ pub static TDG_GATE: [[Complex64; 2]; 2] = [ [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; +pub static DCX_GATE: [[Complex64; 4]; 4] = [ + [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], + [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], +]; + #[inline] pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] { [[c64(0., theta).exp()]] diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 92700f3274e7..632f5b0f5737 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -183,7 +183,7 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // C4XGate = 44 ["placeholder", "placeholder"], // DCXGate = 45 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], // CCZGate = 46 ["placeholder", "placeholder"], // RCCXGate = 47 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index af7dabc86216..d9626b5c7371 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -240,7 +240,7 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29 34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39 - 2, 2, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 2, 2, 34, 34, 34, 2, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -250,7 +250,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29 34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39 - 1, 3, 34, 34, 34, 34, 34, 34, 34, 34, // 40-49 + 1, 3, 34, 34, 34, 0, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -523,7 +523,10 @@ impl Operation for StandardGate { Self::CSwapGate => todo!(), Self::CUGate | Self::CU1Gate | Self::CU3Gate => todo!(), Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), - Self::DCXGate => todo!(), + Self::DCXGate => match params { + [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), + _ => None, + }, Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), @@ -965,7 +968,21 @@ impl Operation for StandardGate { Self::CU1Gate => todo!(), Self::CU3Gate => todo!(), Self::C3XGate | Self::C3SXGate | Self::C4XGate => todo!(), - Self::DCXGate => todo!(), + Self::DCXGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CCZGate => todo!(), Self::RCCXGate | Self::RC3XGate => todo!(), Self::RXXGate | Self::RYYGate | Self::RZZGate => todo!(), diff --git a/qiskit/circuit/library/standard_gates/dcx.py b/qiskit/circuit/library/standard_gates/dcx.py index 6455bea2779e..d83f2e2f9c7f 100644 --- a/qiskit/circuit/library/standard_gates/dcx.py +++ b/qiskit/circuit/library/standard_gates/dcx.py @@ -15,6 +15,7 @@ from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit._utils import with_gate_array +from qiskit._accelerate.circuit import StandardGate @with_gate_array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0]]) @@ -48,6 +49,8 @@ class DCXGate(SingletonGate): \end{pmatrix} """ + _standard_gate = StandardGate.DCXGate + def __init__(self, label=None, *, duration=None, unit="dt"): """Create new DCX gate.""" super().__init__("dcx", 2, [], label=label, duration=duration, unit=unit) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 8b3ff7bf1979..08bac04c9e6f 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5328,9 +5328,7 @@ def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: Returns: A handle to the instructions created. """ - from .library.standard_gates.dcx import DCXGate - - return self.append(DCXGate(), [qubit1, qubit2], [], copy=False) + return self._append_standard_gate(op=StandardGate.DCXGate, qargs=[qubit1, qubit2]) def ccx( self, From 6447941885254066371b674334e68ee153e5b329 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 27 Jun 2024 05:08:24 -0400 Subject: [PATCH 177/179] Implement RGate in Rust (#12662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement RGate in Rust * Update crates/circuit/src/operations.rs Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Fix error in decomposition of RGate There is an error in the expression for decomposition of the R gate in the port to Rust. This fixes the error and re-enables the skipped test that failed because of the incorrect expression. * Factor cloning the Param enum in Rust To clone the enum, each variant must be handled separately. This is currently used once, but can be used each time a `Param` is cloned. In case more work needs to be done within match arms, one might choose not to use this function, but rather clone in each of these arms. * Run cargo fmt * Implement and use addition for enum Param This handles `Float` and `ParameterExpression` variants uniformly. --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/gate_matrix.rs | 13 ++++++ crates/circuit/src/imports.rs | 2 +- crates/circuit/src/operations.rs | 52 +++++++++++++++++++--- qiskit/circuit/library/standard_gates/r.py | 3 ++ qiskit/circuit/quantumcircuit.py | 4 +- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 80fecfb597c6..2a3fcdf88289 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -24,6 +24,19 @@ const fn c64(re: f64, im: f64) -> Complex64 { pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]]; +#[inline] +pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { + let half_theta = theta / 2.; + let cost = c64(half_theta.cos(), 0.); + let sint = half_theta.sin(); + let cosphi = phi.cos(); + let sinphi = phi.sin(); + [ + [cost, c64(-sint * sinphi, -sint * cosphi)], + [c64(sint * sinphi, -sint * cosphi), cost], + ] +} + #[inline] pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { let half_theta = theta / 2.; diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 632f5b0f5737..bf06685ba53b 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -157,7 +157,7 @@ static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ // CRZGate = 31 ["placeholder", "placeholder"], // RGate 32 - ["placeholder", "placeholder"], + ["qiskit.circuit.library.standard_gates.r", "RGate"], // CHGate = 33 ["qiskit.circuit.library.standard_gates.h", "CHGate"], // CPhaseGate = 34 diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index d9626b5c7371..e0e93726735d 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -239,7 +239,7 @@ static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 1, 1, 1, 2, 2, 2, 1, 1, 1, 34, // 20-29 - 34, 34, 34, 2, 2, 2, 2, 2, 3, 2, // 30-39 + 34, 34, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39 2, 2, 34, 34, 34, 2, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -249,7 +249,7 @@ static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 0, 0, 0, 0, 2, 2, 1, 2, 3, 34, // 20-29 - 34, 34, 34, 0, 1, 0, 0, 0, 0, 3, // 30-39 + 34, 34, 2, 0, 1, 0, 0, 0, 0, 3, // 30-39 1, 3, 34, 34, 34, 0, 34, 34, 34, 34, // 40-49 34, 34, 34, // 50-52 ]; @@ -514,7 +514,12 @@ impl Operation for StandardGate { _ => None, }, Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), - Self::RGate => todo!(), + Self::RGate => match params { + [Param::Float(theta), Param::Float(phi)] => { + Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) + } + _ => None, + }, Self::CHGate => todo!(), Self::CPhaseGate => todo!(), Self::CSGate => todo!(), @@ -957,7 +962,21 @@ impl Operation for StandardGate { ) }), Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), - Self::RGate => todo!(), + Self::RGate => Python::with_gil(|py| -> Option { + let theta_expr = clone_param(¶ms[0], py); + let phi_expr1 = add_param(¶ms[1], -PI2, py); + let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); + let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; + Some( + CircuitData::from_standard_gates( + py, + 1, + [(Self::UGate, defparams, smallvec![Qubit(0)])], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), Self::CHGate => todo!(), Self::CPhaseGate => todo!(), Self::CSGate => todo!(), @@ -997,6 +1016,16 @@ impl Operation for StandardGate { const FLOAT_ZERO: Param = Param::Float(0.0); +// Return explictly requested copy of `param`, handling +// each variant separately. +fn clone_param(param: &Param, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta), + Param::ParameterExpression(theta) => Param::ParameterExpression(theta.clone_ref(py)), + Param::Obj(_) => unreachable!(), + } +} + fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { match param { Param::Float(theta) => Param::Float(*theta * mult), @@ -1004,7 +1033,20 @@ fn multiply_param(param: &Param, mult: f64, py: Python) -> Param { theta .clone_ref(py) .call_method1(py, intern!(py, "__rmul__"), (mult,)) - .expect("Parameter expression for global phase failed"), + .expect("Multiplication of Parameter expression by float failed."), + ), + Param::Obj(_) => unreachable!(), + } +} + +fn add_param(param: &Param, summand: f64, py: Python) -> Param { + match param { + Param::Float(theta) => Param::Float(*theta + summand), + Param::ParameterExpression(theta) => Param::ParameterExpression( + theta + .clone_ref(py) + .call_method1(py, intern!(py, "__add__"), (summand,)) + .expect("Sum of Parameter expression and float failed."), ), Param::Obj(_) => unreachable!(), } diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 9d4905e27866..22c30e24bf6a 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -20,6 +20,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.parameterexpression import ParameterValueType +from qiskit._accelerate.circuit import StandardGate class RGate(Gate): @@ -49,6 +50,8 @@ class RGate(Gate): \end{pmatrix} """ + _standard_gate = StandardGate.RGate + def __init__( self, theta: ParameterValueType, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 08bac04c9e6f..de7a2934a464 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -4649,9 +4649,7 @@ def r( Returns: A handle to the instructions created. """ - from .library.standard_gates.r import RGate - - return self.append(RGate(theta, phi), [qubit], [], copy=False) + return self._append_standard_gate(StandardGate.RGate, [theta, phi], qargs=[qubit]) def rv( self, From 76af5b475b9b2a57f9eeabd3c0b793b037464630 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:58:52 +0200 Subject: [PATCH 178/179] Enable the new efficient MCX decompose (#12628) * enable the new efficient MCX decompose * fix tests * revert explicit * apply review comments * update test_circuit_qasm.py * update test_decompose.py * revert C3X C4X names * fix qasm2 exporter tests use regex to fetch the mcx_ name * fix lint and add reno --------- Co-authored-by: Julien Gacon --- qiskit/circuit/quantumcircuit.py | 4 ++-- .../notes/fix-mcx-performance-de86bcc9f969b81e.yaml | 6 ++++++ test/python/circuit/test_circuit_qasm.py | 12 +++++++----- test/python/circuit/test_controlled_gate.py | 4 ++-- test/python/qasm2/test_export.py | 13 +++++++------ test/python/transpiler/test_decompose.py | 4 ++-- 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index de7a2934a464..485591a8a3bb 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5402,12 +5402,12 @@ def mcx( ValueError: if the given mode is not known, or if too few ancilla qubits are passed. AttributeError: if no ancilla qubits are passed, but some are needed. """ - from .library.standard_gates.x import MCXGrayCode, MCXRecursive, MCXVChain + from .library.standard_gates.x import MCXGate, MCXRecursive, MCXVChain num_ctrl_qubits = len(control_qubits) available_implementations = { - "noancilla": MCXGrayCode(num_ctrl_qubits, ctrl_state=ctrl_state), + "noancilla": MCXGate(num_ctrl_qubits, ctrl_state=ctrl_state), "recursion": MCXRecursive(num_ctrl_qubits, ctrl_state=ctrl_state), "v-chain": MCXVChain(num_ctrl_qubits, False, ctrl_state=ctrl_state), "v-chain-dirty": MCXVChain(num_ctrl_qubits, dirty_ancillas=True, ctrl_state=ctrl_state), diff --git a/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml b/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml new file mode 100644 index 000000000000..8cee3356ac4c --- /dev/null +++ b/releasenotes/notes/fix-mcx-performance-de86bcc9f969b81e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Improve the decomposition of the gate generated by :meth:`.QuantumCircuit.mcx` + without using ancilla qubits, so that the number of :class:`.CXGate` will grow + quadratically in the number of qubits and not exponentially. diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py index c1ece0230d39..13882281cff1 100644 --- a/test/python/circuit/test_circuit_qasm.py +++ b/test/python/circuit/test_circuit_qasm.py @@ -394,12 +394,14 @@ def test_circuit_qasm_with_mcx_gate(self): # qasm output doesn't support parameterized gate yet. # param0 for "gate mcuq(param0) is not used inside the definition - expected_qasm = """OPENQASM 2.0; + pattern = r"""OPENQASM 2.0; include "qelib1.inc"; -gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } -qreg q[4]; -mcx q[0],q[1],q[2],q[3];""" - self.assertEqual(dumps(qc), expected_qasm) +gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; } +gate (?Pmcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; } +qreg q\[4\]; +(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];""" + expected_qasm = re.compile(pattern, re.MULTILINE) + self.assertRegex(dumps(qc), expected_qasm) def test_circuit_qasm_with_mcx_gate_variants(self): """Test circuit qasm() method with MCXGrayCode, MCXRecursive, MCXVChain""" diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 8ba70ee852ca..f26ab987f4fd 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -764,9 +764,9 @@ def test_small_mcx_gates_yield_cx_count(self, num_ctrl_qubits): @data(1, 2, 3, 4) def test_mcxgraycode_gates_yield_explicit_gates(self, num_ctrl_qubits): - """Test creating an mcx gate calls MCXGrayCode and yeilds explicit definition.""" + """Test an MCXGrayCode yields explicit definition.""" qc = QuantumCircuit(num_ctrl_qubits + 1) - qc.mcx(list(range(num_ctrl_qubits)), [num_ctrl_qubits]) + qc.append(MCXGrayCode(num_ctrl_qubits), list(range(qc.num_qubits)), []) explicit = {1: CXGate, 2: CCXGate, 3: C3XGate, 4: C4XGate} self.assertEqual(type(qc[0].operation), explicit[num_ctrl_qubits]) diff --git a/test/python/qasm2/test_export.py b/test/python/qasm2/test_export.py index a0a3ade6ce86..85172ec3ce8f 100644 --- a/test/python/qasm2/test_export.py +++ b/test/python/qasm2/test_export.py @@ -387,13 +387,14 @@ def test_mcx_gate(self): # qasm output doesn't support parameterized gate yet. # param0 for "gate mcuq(param0) is not used inside the definition - expected_qasm = """\ -OPENQASM 2.0; + pattern = r"""OPENQASM 2.0; include "qelib1.inc"; -gate mcx q0,q1,q2,q3 { h q3; p(pi/8) q0; p(pi/8) q1; p(pi/8) q2; p(pi/8) q3; cx q0,q1; p(-pi/8) q1; cx q0,q1; cx q1,q2; p(-pi/8) q2; cx q0,q2; p(pi/8) q2; cx q1,q2; p(-pi/8) q2; cx q0,q2; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q1,q3; p(pi/8) q3; cx q2,q3; p(-pi/8) q3; cx q0,q3; h q3; } -qreg q[4]; -mcx q[0],q[1],q[2],q[3];""" - self.assertEqual(qasm2.dumps(qc), expected_qasm) +gate mcx q0,q1,q2,q3 { h q3; p\(pi/8\) q0; p\(pi/8\) q1; p\(pi/8\) q2; p\(pi/8\) q3; cx q0,q1; p\(-pi/8\) q1; cx q0,q1; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; p\(pi/8\) q2; cx q1,q2; p\(-pi/8\) q2; cx q0,q2; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q1,q3; p\(pi/8\) q3; cx q2,q3; p\(-pi/8\) q3; cx q0,q3; h q3; } +gate (?Pmcx_[0-9]*) q0,q1,q2,q3 { mcx q0,q1,q2,q3; } +qreg q\[4\]; +(?P=mcx_id) q\[0\],q\[1\],q\[2\],q\[3\];""" + expected_qasm = re.compile(pattern, re.MULTILINE) + self.assertRegex(qasm2.dumps(qc), expected_qasm) def test_mcx_gate_variants(self): n = 5 diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 91ebede9fa86..7b364f3ac10f 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -216,7 +216,7 @@ def test_decompose_only_given_label(self): def test_decompose_only_given_name(self): """Test decomposition parameters so that only given name is decomposed.""" - decom_circ = self.complex_circuit.decompose(["mcx"]) + decom_circ = self.complex_circuit.decompose(["mcx"], reps=2) dag = circuit_to_dag(decom_circ) self.assertEqual(len(dag.op_nodes()), 13) @@ -236,7 +236,7 @@ def test_decompose_only_given_name(self): def test_decompose_mixture_of_names_and_labels(self): """Test decomposition parameters so that mixture of names and labels is decomposed""" - decom_circ = self.complex_circuit.decompose(["mcx", "gate2"]) + decom_circ = self.complex_circuit.decompose(["mcx", "gate2"], reps=2) dag = circuit_to_dag(decom_circ) self.assertEqual(len(dag.op_nodes()), 15) From ea5a54b9da8db91a3e67812856ce419bef923822 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 27 Jun 2024 15:38:36 -0400 Subject: [PATCH 179/179] Add constant abbreviations for some values and types. (#12651) This PR introduces some abbreviations for repetitive Rust code. Motivations are reducing clutter, improving readability, and perhaps modest support for rapid development. * Use the definition of `const fn 64` that was introduced in #12459 uniformly in all crates. * Define some complex constants `C_ONE`, `C_ZERO`, `IM`, etc. * Introduce type definitions for arrays representing gates. For example: `GateArray1Q = [[Complex64; 2]; 2];` --- .../accelerate/src/convert_2q_block_matrix.rs | 5 +- .../src/euler_one_qubit_decomposer.rs | 11 +- crates/accelerate/src/isometry.rs | 7 +- crates/accelerate/src/pauli_exp_val.rs | 5 +- crates/accelerate/src/sampled_exp_val.rs | 3 +- crates/accelerate/src/sparse_pauli_op.rs | 29 +- crates/accelerate/src/two_qubit_decompose.rs | 211 +++++---------- crates/accelerate/src/uc_gate.rs | 14 +- crates/circuit/src/gate_matrix.rs | 254 +++++++----------- crates/circuit/src/lib.rs | 1 + crates/circuit/src/operations.rs | 37 +-- crates/circuit/src/util.rs | 48 ++++ 12 files changed, 262 insertions(+), 363 deletions(-) create mode 100644 crates/circuit/src/util.rs diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index e311c129b11b..9c179397d641 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -20,10 +20,7 @@ use numpy::ndarray::{aview2, Array2, ArrayView2}; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; use smallvec::SmallVec; -static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [ - [Complex64::new(1., 0.), Complex64::new(0., 0.)], - [Complex64::new(0., 0.), Complex64::new(1., 0.)], -]; +use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; /// Return the matrix Operator resulting from a block of Instructions. #[pyfunction] diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 1fd5fd7834ff..9f10f76de467 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -31,6 +31,7 @@ use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; +use qiskit_circuit::util::c64; use qiskit_circuit::SliceOrInt; pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; @@ -855,16 +856,16 @@ pub fn params_xyx(unitary: PyReadonlyArray2) -> [f64; 4] { fn params_xzx_inner(umat: ArrayView2) -> [f64; 4] { let det = det_one_qubit(umat); - let phase = (Complex64::new(0., -1.) * det.ln()).re / 2.; + let phase = det.ln().im / 2.; let sqrt_det = det.sqrt(); let mat_zyz = arr2(&[ [ - Complex64::new((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im), - Complex64::new((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), + c64((umat[[0, 0]] / sqrt_det).re, (umat[[1, 0]] / sqrt_det).im), + c64((umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), ], [ - Complex64::new(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), - Complex64::new((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im), + c64(-(umat[[1, 0]] / sqrt_det).re, (umat[[0, 0]] / sqrt_det).im), + c64((umat[[0, 0]] / sqrt_det).re, -(umat[[1, 0]] / sqrt_det).im), ], ]); let [theta, phi, lam, phase_zxz] = params_zxz_inner(mat_zyz.view()); diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs index a3a8be38dae2..ceaba2946b3a 100644 --- a/crates/accelerate/src/isometry.rs +++ b/crates/accelerate/src/isometry.rs @@ -24,6 +24,7 @@ use ndarray::prelude::*; use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2}; use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; +use qiskit_circuit::util::C_ZERO; /// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or /// basis_state=1 respectively @@ -315,11 +316,7 @@ pub fn merge_ucgate_and_diag( .enumerate() .map(|(i, raw_gate)| { let gate = raw_gate.as_array(); - let res = aview2(&[ - [diag[2 * i], Complex64::new(0., 0.)], - [Complex64::new(0., 0.), diag[2 * i + 1]], - ]) - .dot(&gate); + let res = aview2(&[[diag[2 * i], C_ZERO], [C_ZERO, diag[2 * i + 1]]]).dot(&gate); res.into_pyarray_bound(py).into() }) .collect() diff --git a/crates/accelerate/src/pauli_exp_val.rs b/crates/accelerate/src/pauli_exp_val.rs index 52a2fc07f81d..8ee4b019b3e0 100644 --- a/crates/accelerate/src/pauli_exp_val.rs +++ b/crates/accelerate/src/pauli_exp_val.rs @@ -19,6 +19,7 @@ use pyo3::wrap_pyfunction; use rayon::prelude::*; use crate::getenv_use_multiple_threads; +use qiskit_circuit::util::c64; const PARALLEL_THRESHOLD: usize = 19; @@ -88,7 +89,7 @@ pub fn expval_pauli_with_x( let index_0 = ((i << 1) & mask_u) | (i & mask_l); let index_1 = index_0 ^ x_mask; let val_0 = (phase - * Complex64::new( + * c64( data_arr[index_1].re * data_arr[index_0].re + data_arr[index_1].im * data_arr[index_0].im, data_arr[index_1].im * data_arr[index_0].re @@ -96,7 +97,7 @@ pub fn expval_pauli_with_x( )) .re; let val_1 = (phase - * Complex64::new( + * c64( data_arr[index_0].re * data_arr[index_1].re + data_arr[index_0].im * data_arr[index_1].im, data_arr[index_0].im * data_arr[index_1].re diff --git a/crates/accelerate/src/sampled_exp_val.rs b/crates/accelerate/src/sampled_exp_val.rs index b51ca3c98f0e..0b8836a94165 100644 --- a/crates/accelerate/src/sampled_exp_val.rs +++ b/crates/accelerate/src/sampled_exp_val.rs @@ -18,6 +18,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; use crate::pauli_exp_val::fast_sum; +use qiskit_circuit::util::c64; const OPER_TABLE_SIZE: usize = (b'Z' as usize) + 1; const fn generate_oper_table() -> [[f64; 2]; OPER_TABLE_SIZE] { @@ -81,7 +82,7 @@ pub fn sampled_expval_complex( let out: Complex64 = oper_strs .into_iter() .enumerate() - .map(|(idx, string)| coeff_arr[idx] * Complex64::new(bitstring_expval(&dist, string), 0.)) + .map(|(idx, string)| coeff_arr[idx] * c64(bitstring_expval(&dist, string), 0.)) .sum(); Ok(out.re) } diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index e0c80f716161..8a51d8ee781c 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -23,6 +23,7 @@ use hashbrown::HashMap; use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis}; use num_complex::Complex64; use num_traits::Zero; +use qiskit_circuit::util::{c64, C_ONE, C_ZERO}; use rayon::prelude::*; use crate::rayon_ext::*; @@ -257,9 +258,9 @@ impl<'py> ZXPaulisView<'py> { let ys = (xs & zs).count_ones(); match (phase as u32 + ys) % 4 { 0 => coeff, - 1 => Complex64::new(coeff.im, -coeff.re), - 2 => Complex64::new(-coeff.re, -coeff.im), - 3 => Complex64::new(-coeff.im, coeff.re), + 1 => c64(coeff.im, -coeff.re), + 2 => c64(-coeff.re, -coeff.im), + 3 => c64(-coeff.im, coeff.re), _ => unreachable!(), } }) @@ -311,10 +312,10 @@ impl MatrixCompressedPaulis { .zip(self.z_like.drain(..)) .zip(self.coeffs.drain(..)) { - *hash_table.entry(key).or_insert(Complex64::new(0.0, 0.0)) += coeff; + *hash_table.entry(key).or_insert(C_ZERO) += coeff; } for ((x, z), coeff) in hash_table { - if coeff == Complex64::new(0.0, 0.0) { + if coeff.is_zero() { continue; } self.x_like.push(x); @@ -347,7 +348,7 @@ pub fn decompose_dense( let mut coeffs = vec![]; if num_qubits > 0 { decompose_dense_inner( - Complex64::new(1.0, 0.0), + C_ONE, num_qubits, &[], operator.as_array(), @@ -532,7 +533,7 @@ fn to_matrix_dense_inner(paulis: &MatrixCompressedPaulis, parallel: bool) -> Vec // Doing the initialization here means that when we're in parallel contexts, we do the // zeroing across the whole threadpool. This also seems to give a speed-up in serial // contexts, but I don't understand that. ---Jake - row.fill(Complex64::new(0.0, 0.0)); + row.fill(C_ZERO); for ((&x_like, &z_like), &coeff) in paulis .x_like .iter() @@ -667,7 +668,7 @@ macro_rules! impl_to_matrix_sparse { ((i_row as $uint_ty) ^ (paulis.x_like[a] as $uint_ty)) .cmp(&((i_row as $uint_ty) ^ (paulis.x_like[b] as $uint_ty))) }); - let mut running = Complex64::new(0.0, 0.0); + let mut running = C_ZERO; let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize); for (x_like, z_like, coeff) in order .iter() @@ -748,7 +749,7 @@ macro_rules! impl_to_matrix_sparse { (i_row as $uint_ty ^ paulis.x_like[a] as $uint_ty) .cmp(&(i_row as $uint_ty ^ paulis.x_like[b] as $uint_ty)) }); - let mut running = Complex64::new(0.0, 0.0); + let mut running = C_ZERO; let mut prev_index = i_row ^ (paulis.x_like[order[0]] as usize); for (x_like, z_like, coeff) in order .iter() @@ -844,11 +845,11 @@ mod tests { // Deliberately using multiples of small powers of two so the floating-point addition // of them is associative. coeffs: vec![ - Complex64::new(0.25, 0.5), - Complex64::new(0.125, 0.25), - Complex64::new(0.375, 0.125), - Complex64::new(-0.375, 0.0625), - Complex64::new(-0.5, -0.25), + c64(0.25, 0.5), + c64(0.125, 0.25), + c64(0.375, 0.125), + c64(-0.375, 0.0625), + c64(-0.5, -0.25), ], } } diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index e8c572b04039..8637cb03c735 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -52,67 +52,28 @@ use rand_distr::StandardNormal; use rand_pcg::Pcg64Mcg; use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE}; +use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM}; use qiskit_circuit::SliceOrInt; -const PI2: f64 = PI / 2.0; -const PI4: f64 = PI / 4.0; +const PI2: f64 = PI / 2.; +const PI4: f64 = PI / 4.; const PI32: f64 = 3.0 * PI2; const TWO_PI: f64 = 2.0 * PI; const C1: c64 = c64 { re: 1.0, im: 0.0 }; -static B_NON_NORMALIZED: [[Complex64; 4]; 4] = [ - [ - Complex64::new(1.0, 0.), - Complex64::new(0., 1.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 1.), - Complex64::new(1.0, 0.0), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 1.), - Complex64::new(-1., 0.), - ], - [ - Complex64::new(1., 0.), - Complex64::new(0., -1.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - ], +static B_NON_NORMALIZED: GateArray2Q = [ + [C_ONE, IM, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, IM, C_ONE], + [C_ZERO, C_ZERO, IM, C_M_ONE], + [C_ONE, M_IM, C_ZERO, C_ZERO], ]; -static B_NON_NORMALIZED_DAGGER: [[Complex64; 4]; 4] = [ - [ - Complex64::new(0.5, 0.), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0.5, 0.0), - ], - [ - Complex64::new(0., -0.5), - Complex64::new(0., 0.), - Complex64::new(0., 0.), - Complex64::new(0., 0.5), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0., -0.5), - Complex64::new(0., -0.5), - Complex64::new(0., 0.), - ], - [ - Complex64::new(0., 0.), - Complex64::new(0.5, 0.), - Complex64::new(-0.5, 0.), - Complex64::new(0., 0.), - ], +static B_NON_NORMALIZED_DAGGER: GateArray2Q = [ + [c64(0.5, 0.), C_ZERO, C_ZERO, c64(0.5, 0.)], + [c64(0., -0.5), C_ZERO, C_ZERO, c64(0., 0.5)], + [C_ZERO, c64(0., -0.5), c64(0., -0.5), C_ZERO], + [C_ZERO, c64(0.5, 0.), c64(-0.5, 0.), C_ZERO], ]; enum MagicBasisTransform { @@ -318,29 +279,26 @@ fn closest_partial_swap(a: f64, b: f64, c: f64) -> f64 { fn rx_matrix(theta: f64) -> Array2 { let half_theta = theta / 2.; - let cos = Complex64::new(half_theta.cos(), 0.); - let isin = Complex64::new(0., -half_theta.sin()); + let cos = c64(half_theta.cos(), 0.); + let isin = c64(0., -half_theta.sin()); array![[cos, isin], [isin, cos]] } fn ry_matrix(theta: f64) -> Array2 { let half_theta = theta / 2.; - let cos = Complex64::new(half_theta.cos(), 0.); - let sin = Complex64::new(half_theta.sin(), 0.); + let cos = c64(half_theta.cos(), 0.); + let sin = c64(half_theta.sin(), 0.); array![[cos, -sin], [sin, cos]] } fn rz_matrix(theta: f64) -> Array2 { - let ilam2 = Complex64::new(0., 0.5 * theta); - array![ - [(-ilam2).exp(), Complex64::new(0., 0.)], - [Complex64::new(0., 0.), ilam2.exp()] - ] + let ilam2 = c64(0., 0.5 * theta); + array![[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] } fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2 { let identity = aview2(&ONE_QUBIT_IDENTITY); - let phase = Complex64::new(0., global_phase).exp(); + let phase = c64(0., global_phase).exp(); let mut matrix = Array2::from_diag(&arr1(&[phase, phase, phase, phase])); sequence .iter() @@ -375,7 +333,6 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2< } const DEFAULT_FIDELITY: f64 = 1.0 - 1.0e-9; -const C1_IM: Complex64 = Complex64::new(0.0, 1.0); #[derive(Clone, Debug, Copy)] #[pyclass(module = "qiskit._accelerate.two_qubit_decompose")] @@ -500,18 +457,9 @@ impl TwoQubitWeylDecomposition { } } -static IPZ: [[Complex64; 2]; 2] = [ - [C1_IM, Complex64::new(0., 0.)], - [Complex64::new(0., 0.), Complex64::new(0., -1.)], -]; -static IPY: [[Complex64; 2]; 2] = [ - [Complex64::new(0., 0.), Complex64::new(1., 0.)], - [Complex64::new(-1., 0.), Complex64::new(0., 0.)], -]; -static IPX: [[Complex64; 2]; 2] = [ - [Complex64::new(0., 0.), C1_IM], - [C1_IM, Complex64::new(0., 0.)], -]; +static IPZ: GateArray1Q = [[IM, C_ZERO], [C_ZERO, M_IM]]; +static IPY: GateArray1Q = [[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; +static IPX: GateArray1Q = [[C_ZERO, IM], [IM, C_ZERO]]; #[pymethods] impl TwoQubitWeylDecomposition { @@ -671,7 +619,7 @@ impl TwoQubitWeylDecomposition { temp.diag_mut() .iter_mut() .enumerate() - .for_each(|(index, x)| *x = (C1_IM * d[index]).exp()); + .for_each(|(index, x)| *x = (IM * d[index]).exp()); let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), MagicBasisTransform::Into); let k2 = magic_basis_transform(p.t(), MagicBasisTransform::Into); @@ -737,7 +685,7 @@ impl TwoQubitWeylDecomposition { let is_close = |ap: f64, bp: f64, cp: f64| -> bool { let [da, db, dc] = [a - ap, b - bp, c - cp]; let tr = 4. - * Complex64::new( + * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ); @@ -1016,13 +964,13 @@ impl TwoQubitWeylDecomposition { b - specialized.b, -c - specialized.c, ]; - 4. * Complex64::new( + 4. * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) } else { let [da, db, dc] = [a - specialized.a, b - specialized.b, c - specialized.c]; - 4. * Complex64::new( + 4. * c64( da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) @@ -1597,20 +1545,14 @@ impl TwoQubitBasisDecomposer { } } -static K12R_ARR: [[Complex64; 2]; 2] = [ - [ - Complex64::new(0., FRAC_1_SQRT_2), - Complex64::new(FRAC_1_SQRT_2, 0.), - ], - [ - Complex64::new(-FRAC_1_SQRT_2, 0.), - Complex64::new(0., -FRAC_1_SQRT_2), - ], +static K12R_ARR: GateArray1Q = [ + [c64(0., FRAC_1_SQRT_2), c64(FRAC_1_SQRT_2, 0.)], + [c64(-FRAC_1_SQRT_2, 0.), c64(0., -FRAC_1_SQRT_2)], ]; -static K12L_ARR: [[Complex64; 2]; 2] = [ - [Complex64::new(0.5, 0.5), Complex64::new(0.5, 0.5)], - [Complex64::new(-0.5, 0.5), Complex64::new(0.5, -0.5)], +static K12L_ARR: GateArray1Q = [ + [c64(0.5, 0.5), c64(0.5, 0.5)], + [c64(-0.5, 0.5), c64(0.5, -0.5)], ]; fn decomp0_inner(target: &TwoQubitWeylDecomposition) -> SmallVec<[Array2; 8]> { @@ -1650,90 +1592,71 @@ impl TwoQubitBasisDecomposer { // Create some useful matrices U1, U2, U3 are equivalent to the basis, // expand as Ui = Ki1.Ubasis.Ki2 let b = basis_decomposer.b; - let temp = Complex64::new(0.5, -0.5); + let temp = c64(0.5, -0.5); let k11l = array![ - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., -b).exp()), - temp * Complex64::new(0., -b).exp() - ], - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()), - temp * -(Complex64::new(0., b).exp()) - ], + [temp * (M_IM * c64(0., -b).exp()), temp * c64(0., -b).exp()], + [temp * (M_IM * c64(0., b).exp()), temp * -(c64(0., b).exp())], ]; let k11r = array![ [ - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * Complex64::new(0., -b).exp()), - FRAC_1_SQRT_2 * -Complex64::new(0., -b).exp() + FRAC_1_SQRT_2 * (IM * c64(0., -b).exp()), + FRAC_1_SQRT_2 * -c64(0., -b).exp() ], [ - FRAC_1_SQRT_2 * Complex64::new(0., b).exp(), - FRAC_1_SQRT_2 * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()) + FRAC_1_SQRT_2 * c64(0., b).exp(), + FRAC_1_SQRT_2 * (M_IM * c64(0., b).exp()) ], ]; let k12l = aview2(&K12L_ARR); let k12r = aview2(&K12R_ARR); let k32l_k21l = array![ [ - FRAC_1_SQRT_2 * Complex64::new(1., (2. * b).cos()), - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * (2. * b).sin()) + FRAC_1_SQRT_2 * c64(1., (2. * b).cos()), + FRAC_1_SQRT_2 * (IM * (2. * b).sin()) ], [ - FRAC_1_SQRT_2 * (Complex64::new(0., 1.) * (2. * b).sin()), - FRAC_1_SQRT_2 * Complex64::new(1., -(2. * b).cos()) + FRAC_1_SQRT_2 * (IM * (2. * b).sin()), + FRAC_1_SQRT_2 * c64(1., -(2. * b).cos()) ], ]; - let temp = Complex64::new(0.5, 0.5); + let temp = c64(0.5, 0.5); let k21r = array![ [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., -2. * b).exp()), - temp * Complex64::new(0., -2. * b).exp() + temp * (M_IM * c64(0., -2. * b).exp()), + temp * c64(0., -2. * b).exp() ], [ - temp * (Complex64::new(0., 1.) * Complex64::new(0., 2. * b).exp()), - temp * Complex64::new(0., 2. * b).exp() + temp * (IM * c64(0., 2. * b).exp()), + temp * c64(0., 2. * b).exp() ], ]; - const K22L_ARR: [[Complex64; 2]; 2] = [ - [ - Complex64::new(FRAC_1_SQRT_2, 0.), - Complex64::new(-FRAC_1_SQRT_2, 0.), - ], - [ - Complex64::new(FRAC_1_SQRT_2, 0.), - Complex64::new(FRAC_1_SQRT_2, 0.), - ], + const K22L_ARR: GateArray1Q = [ + [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)], + [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)], ]; let k22l = aview2(&K22L_ARR); - let k22r_arr: [[Complex64; 2]; 2] = [ - [Complex64::zero(), Complex64::new(1., 0.)], - [Complex64::new(-1., 0.), Complex64::zero()], - ]; + let k22r_arr: GateArray1Q = [[Complex64::zero(), C_ONE], [C_M_ONE, Complex64::zero()]]; let k22r = aview2(&k22r_arr); let k31l = array![ [ - FRAC_1_SQRT_2 * Complex64::new(0., -b).exp(), - FRAC_1_SQRT_2 * Complex64::new(0., -b).exp() + FRAC_1_SQRT_2 * c64(0., -b).exp(), + FRAC_1_SQRT_2 * c64(0., -b).exp() ], [ - FRAC_1_SQRT_2 * -Complex64::new(0., b).exp(), - FRAC_1_SQRT_2 * Complex64::new(0., b).exp() + FRAC_1_SQRT_2 * -c64(0., b).exp(), + FRAC_1_SQRT_2 * c64(0., b).exp() ], ]; - let temp = Complex64::new(0., 1.); let k31r = array![ - [temp * Complex64::new(0., b).exp(), Complex64::zero()], - [Complex64::zero(), temp * -Complex64::new(0., -b).exp()], + [IM * c64(0., b).exp(), Complex64::zero()], + [Complex64::zero(), M_IM * c64(0., -b).exp()], ]; - let temp = Complex64::new(0.5, 0.5); + let temp = c64(0.5, 0.5); let k32r = array![ + [temp * c64(0., b).exp(), temp * -c64(0., -b).exp()], [ - temp * Complex64::new(0., b).exp(), - temp * -Complex64::new(0., -b).exp() - ], - [ - temp * (Complex64::new(0., -1.) * Complex64::new(0., b).exp()), - temp * (Complex64::new(0., -1.) * Complex64::new(0., -b).exp()) + temp * (M_IM * c64(0., b).exp()), + temp * (M_IM * c64(0., -b).exp()) ], ]; let k1ld = transpose_conjugate(basis_decomposer.K1l.view()); @@ -1793,11 +1716,11 @@ impl TwoQubitBasisDecomposer { fn traces(&self, target: &TwoQubitWeylDecomposition) -> [Complex64; 4] { [ - 4. * Complex64::new( + 4. * c64( target.a.cos() * target.b.cos() * target.c.cos(), target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * Complex64::new( + 4. * c64( (PI4 - target.a).cos() * (self.basis_decomposer.b - target.b).cos() * target.c.cos(), @@ -1805,8 +1728,8 @@ impl TwoQubitBasisDecomposer { * (self.basis_decomposer.b - target.b).sin() * target.c.sin(), ), - Complex64::new(4. * target.c.cos(), 0.), - Complex64::new(4., 0.), + c64(4. * target.c.cos(), 0.), + c64(4., 0.), ] } diff --git a/crates/accelerate/src/uc_gate.rs b/crates/accelerate/src/uc_gate.rs index 3a5f74a6f0b1..21fd7fa04656 100644 --- a/crates/accelerate/src/uc_gate.rs +++ b/crates/accelerate/src/uc_gate.rs @@ -21,14 +21,14 @@ use ndarray::prelude::*; use numpy::{IntoPyArray, PyReadonlyArray2}; use crate::euler_one_qubit_decomposer::det_one_qubit; +use qiskit_circuit::util::{c64, C_ZERO, IM}; -const PI2: f64 = PI / 2.; const EPS: f64 = 1e-10; // These constants are the non-zero elements of an RZ gate's unitary with an // angle of pi / 2 -const RZ_PI2_11: Complex64 = Complex64::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2); -const RZ_PI2_00: Complex64 = Complex64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2); +const RZ_PI2_11: Complex64 = c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2); +const RZ_PI2_00: Complex64 = c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2); /// This method implements the decomposition given in equation (3) in /// https://arxiv.org/pdf/quant-ph/0410066.pdf. @@ -48,10 +48,10 @@ fn demultiplex_single_uc( let x11 = x[[0, 0]] / det_x.sqrt(); let phi = det_x.arg(); - let r1 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. - x11.arg())).exp(); - let r2 = (Complex64::new(0., 1.) / 2. * (PI2 - phi / 2. + x11.arg() + PI)).exp(); + let r1 = (IM / 2. * (PI / 2. - phi / 2. - x11.arg())).exp(); + let r2 = (IM / 2. * (PI / 2. - phi / 2. + x11.arg() + PI)).exp(); - let r = array![[r1, Complex64::new(0., 0.)], [Complex64::new(0., 0.), r2],]; + let r = array![[r1, C_ZERO], [C_ZERO, r2],]; let decomp = r .dot(&x) @@ -67,7 +67,7 @@ fn demultiplex_single_uc( // If d is not equal to diag(i,-i), then we put it into this "standard" form // (see eq. (13) in https://arxiv.org/pdf/quant-ph/0410066.pdf) by interchanging // the eigenvalues and eigenvectors - if (diag[0] + Complex64::new(0., 1.)).abs() < EPS { + if (diag[0] + IM).abs() < EPS { diag = diag.slice(s![..;-1]).to_owned(); u = u.slice(s![.., ..;-1]).to_owned(); } diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 2a3fcdf88289..2f085ea79c0a 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -10,22 +10,16 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use num_complex::Complex64; use std::f64::consts::FRAC_1_SQRT_2; -// num-complex exposes an equivalent function but it's not a const function -// so it's not compatible with static definitions. This is a const func and -// just reduces the amount of typing we need. -#[inline(always)] -const fn c64(re: f64, im: f64) -> Complex64 { - Complex64::new(re, im) -} +use crate::util::{ + c64, GateArray0Q, GateArray1Q, GateArray2Q, GateArray3Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM, +}; -pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = - [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]]; +pub static ONE_QUBIT_IDENTITY: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_ONE]]; #[inline] -pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { +pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { let half_theta = theta / 2.; let cost = c64(half_theta.cos(), 0.); let sint = half_theta.sin(); @@ -38,7 +32,7 @@ pub fn r_gate(theta: f64, phi: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn rx_gate(theta: f64) -> GateArray1Q { let half_theta = theta / 2.; let cos = c64(half_theta.cos(), 0.); let isin = c64(0., -half_theta.sin()); @@ -46,7 +40,7 @@ pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn ry_gate(theta: f64) -> GateArray1Q { let half_theta = theta / 2.; let cos = c64(half_theta.cos(), 0.); let sin = c64(half_theta.sin(), 0.); @@ -54,213 +48,150 @@ pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn rz_gate(theta: f64) -> [[Complex64; 2]; 2] { +pub fn rz_gate(theta: f64) -> GateArray1Q { let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), c64(0., 0.)], [c64(0., 0.), ilam2.exp()]] + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] } -pub static H_GATE: [[Complex64; 2]; 2] = [ +pub static H_GATE: GateArray1Q = [ [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)], [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)], ]; -pub static CX_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], +pub static CX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], ]; -pub static SX_GATE: [[Complex64; 2]; 2] = [ +pub static SX_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], [c64(0.5, -0.5), c64(0.5, 0.5)], ]; -pub static SXDG_GATE: [[Complex64; 2]; 2] = [ +pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, -0.5), c64(0.5, 0.5)], [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static X_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(1., 0.)], [c64(1., 0.), c64(0., 0.)]]; +pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; -pub static Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]]; +pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; -pub static Y_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(0., -1.)], [c64(0., 1.), c64(0., 0.)]]; +pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; -pub static CZ_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(-1., 0.)], +pub static CZ_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_M_ONE], ]; -pub static CY_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(0., -1.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)], +pub static CY_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, M_IM], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, IM, C_ZERO, C_ZERO], ]; -pub static CCX_GATE: [[Complex64; 8]; 8] = [ +pub static CCX_GATE: GateArray3Q = [ [ - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, ], [ - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(1., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, C_ZERO, C_ZERO, C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, ], ]; -pub static ECR_GATE: [[Complex64; 4]; 4] = [ +pub static ECR_GATE: GateArray2Q = [ [ - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, c64(0., FRAC_1_SQRT_2), ], [ c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, c64(0., -FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, ], [ - c64(0., 0.), + C_ZERO, c64(0., FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), ], [ c64(0., -FRAC_1_SQRT_2), - c64(0., 0.), + C_ZERO, c64(FRAC_1_SQRT_2, 0.), - c64(0., 0.), + C_ZERO, ], ]; -pub static SWAP_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], +pub static SWAP_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static ISWAP_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 1.), c64(0., 0.)], - [c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], +pub static ISWAP_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, IM, C_ZERO], + [C_ZERO, IM, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; -pub static S_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., 1.)]]; +pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; -pub static SDG_GATE: [[Complex64; 2]; 2] = - [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(0., -1.)]]; +pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; -pub static T_GATE: [[Complex64; 2]; 2] = [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)], -]; +pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; -pub static TDG_GATE: [[Complex64; 2]; 2] = [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], +pub static TDG_GATE: GateArray1Q = [ + [C_ONE, C_ZERO], + [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], ]; -pub static DCX_GATE: [[Complex64; 4]; 4] = [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], +pub static DCX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], ]; #[inline] -pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] { +pub fn global_phase_gate(theta: f64) -> GateArray0Q { [[c64(0., theta).exp()]] } #[inline] -pub fn phase_gate(lam: f64) -> [[Complex64; 2]; 2] { - [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., lam).exp()], - ] +pub fn phase_gate(lam: f64) -> GateArray1Q { + [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } #[inline] -pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ @@ -270,37 +201,34 @@ pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { +pub fn xx_minus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ [ c64(cos, 0.), - c64(0., 0.), - c64(0., 0.), + C_ZERO, + C_ZERO, c64(0., -sin) * c64(0., -beta).exp(), ], - [c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], [ c64(0., -sin) * c64(0., beta).exp(), - c64(0., 0.), - c64(0., 0.), + C_ZERO, + C_ZERO, c64(cos, 0.), ], ] } #[inline] -pub fn u1_gate(lam: f64) -> [[Complex64; 2]; 2] { - [ - [c64(1., 0.), c64(0., 0.)], - [c64(0., 0.), c64(0., lam).exp()], - ] +pub fn u1_gate(lam: f64) -> GateArray1Q { + [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } #[inline] -pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u2_gate(phi: f64, lam: f64) -> GateArray1Q { [ [ c64(FRAC_1_SQRT_2, 0.), @@ -314,7 +242,7 @@ pub fn u2_gate(phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { +pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ @@ -324,23 +252,23 @@ pub fn u3_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] { } #[inline] -pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> [[Complex64; 4]; 4] { +pub fn xx_plus_yy_gate(theta: f64, beta: f64) -> GateArray2Q { let cos = (theta / 2.).cos(); let sin = (theta / 2.).sin(); [ - [c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)], + [C_ONE, C_ZERO, C_ZERO, C_ZERO], [ - c64(0., 0.), + C_ZERO, c64(cos, 0.), c64(0., -sin) * c64(0., -beta).exp(), - c64(0., 0.), + C_ZERO, ], [ - c64(0., 0.), + C_ZERO, c64(0., -sin) * c64(0., beta).exp(), c64(cos, 0.), - c64(0., 0.), + C_ZERO, ], - [c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], ] } diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index d7f285911750..9fcaa36480cf 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -17,6 +17,7 @@ pub mod gate_matrix; pub mod imports; pub mod operations; pub mod parameter_table; +pub mod util; mod bit_data; mod interner; diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index e0e93726735d..ff730744c80f 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -24,9 +24,6 @@ use pyo3::prelude::*; use pyo3::{intern, IntoPy, Python}; use smallvec::smallvec; -const PI2: f64 = PI / 2.0; -const PI4: f64 = PI / 4.0; - /// Valid types for an operation field in a CircuitInstruction /// /// These are basically the types allowed in a QuantumCircuit @@ -563,7 +560,11 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![Param::Float(PI), Param::Float(PI2), Param::Float(PI2),], + smallvec![ + Param::Float(PI), + Param::Float(PI / 2.), + Param::Float(PI / 2.), + ], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -732,7 +733,7 @@ impl Operation for StandardGate { 1, [( Self::UGate, - smallvec![Param::Float(PI2), Param::Float(0.), Param::Float(PI)], + smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -763,7 +764,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(PI2)], + smallvec![Param::Float(PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -793,7 +794,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(-PI2)], + smallvec![Param::Float(-PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -823,7 +824,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(PI4)], + smallvec![Param::Float(PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -853,7 +854,7 @@ impl Operation for StandardGate { 1, [( Self::PhaseGate, - smallvec![Param::Float(-PI4)], + smallvec![Param::Float(-PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -895,9 +896,9 @@ impl Operation for StandardGate { smallvec![multiply_param(beta, -1.0, py)], q1.clone(), ), - (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), (Self::SXGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), (Self::SGate, smallvec![], q1.clone()), (Self::CXGate, smallvec![], q0_1.clone()), ( @@ -912,9 +913,9 @@ impl Operation for StandardGate { ), (Self::CXGate, smallvec![], q0_1), (Self::SdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), (Self::SXdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q0), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), (Self::RZGate, smallvec![beta.clone()], q1), ], FLOAT_ZERO, @@ -934,9 +935,9 @@ impl Operation for StandardGate { 2, [ (Self::RZGate, smallvec![beta.clone()], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), (Self::SXGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), (Self::SGate, smallvec![], q0.clone()), (Self::CXGate, smallvec![], q1_0.clone()), ( @@ -951,9 +952,9 @@ impl Operation for StandardGate { ), (Self::CXGate, smallvec![], q1_0), (Self::SdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI2)], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), (Self::SXdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI2)], q1), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), ], FLOAT_ZERO, @@ -964,7 +965,7 @@ impl Operation for StandardGate { Self::CRXGate | Self::CRYGate | Self::CRZGate => todo!(), Self::RGate => Python::with_gil(|py| -> Option { let theta_expr = clone_param(¶ms[0], py); - let phi_expr1 = add_param(¶ms[1], -PI2, py); + let phi_expr1 = add_param(¶ms[1], -PI / 2., py); let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; Some( diff --git a/crates/circuit/src/util.rs b/crates/circuit/src/util.rs new file mode 100644 index 000000000000..11562b0a48cd --- /dev/null +++ b/crates/circuit/src/util.rs @@ -0,0 +1,48 @@ +// 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 num_complex::Complex64; + +// This is a very conservative version of an abbreviation for constructing new Complex64. +// A couple of alternatives to this function are +// `c64, V: Into>(re: T, im: V) -> Complex64` +// Disadvantages are: +// 1. Some people don't like that this allows things like `c64(1, 0)`. Presumably, +// they prefer a more explicit construction. +// 2. This will not work in `const` and `static` constructs. +// Another alternative is +// macro_rules! c64 { +// ($re: expr, $im: expr $(,)*) => { +// Complex64::new($re as f64, $im as f64) +// }; +// Advantages: This allows things like `c64!(1, 2.0)`, including in +// `static` and `const` constructs. +// Disadvantages: +// 1. Three characters `c64!` rather than two `c64`. +// 2. Some people prefer the opposite of the advantages, i.e. more explicitness. +/// Create a new [`Complex`] +#[inline(always)] +pub const fn c64(re: f64, im: f64) -> Complex64 { + Complex64::new(re, im) +} + +pub type GateArray0Q = [[Complex64; 1]; 1]; +pub type GateArray1Q = [[Complex64; 2]; 2]; +pub type GateArray2Q = [[Complex64; 4]; 4]; +pub type GateArray3Q = [[Complex64; 8]; 8]; + +// Use prefix `C_` to distinguish from real, for example +pub const C_ZERO: Complex64 = c64(0., 0.); +pub const C_ONE: Complex64 = c64(1., 0.); +pub const C_M_ONE: Complex64 = c64(-1., 0.); +pub const IM: Complex64 = c64(0., 1.); +pub const M_IM: Complex64 = c64(0., -1.);
' % result + ret = f'
{title} ' + ret += f'
' ret += "
" return ret @@ -119,11 +119,7 @@ def diff_images(self): if os.path.exists(os.path.join(SWD, fullpath_reference)): ratio, diff_name = Results._similarity_ratio(fullpath_name, fullpath_reference) - title = "{} | {} | ratio: {}".format( - name, - self.data[name]["testname"], - ratio, - ) + title = f"{name} | {self.data[name]['testname']} | ratio: {ratio}" if ratio == 1: self.exact_match.append(fullpath_name) else: @@ -158,8 +154,9 @@ def _repr_html_(self): ) else: title = ( - 'Download this image to %s' - " and add/push to the repo