diff --git a/.azure/lint-linux.yml b/.azure/lint-linux.yml index aac89bc005ce..5b79db09ae1b 100644 --- a/.azure/lint-linux.yml +++ b/.azure/lint-linux.yml @@ -43,6 +43,8 @@ jobs: - bash: | set -e source test-job/bin/activate + echo "Running ruff" + ruff qiskit test tools examples setup.py echo "Running pylint" pylint -rn qiskit test tools echo "Running Cargo Clippy" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73eacde8cf57..49d0d0ee376a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -422,25 +422,34 @@ Note: If you have run `test/ipynb/mpl_tester.ipynb` locally it is possible some ## Style and lint -Qiskit Terra uses 2 tools for verify code formatting and lint checking. The +Qiskit Terra uses three tools for verify code formatting and lint checking. The first tool is [black](https://github.com/psf/black) which is a code formatting tool that will automatically update the code formatting to a consistent style. The second tool is [pylint](https://www.pylint.org/) which is a code linter which does a deeper analysis of the Python code to find both style issues and -potential bugs and other common issues in Python. - -You can check that your local modifications conform to the style rules -by running `tox -elint` which will run `black` and `pylint` to check the local -code formatting and lint. If black returns a code formatting error you can -run `tox -eblack` to automatically update the code formatting to conform to -the style. However, if `pylint` returns any error you will have to fix these -issues by manually updating your code. - -Because `pylint` analysis can be slow, there is also a `tox -elint-incr` target, which only applies -`pylint` to files which have changed from the source github. On rare occasions this will miss some -issues that would have been caught by checking the complete source tree, but makes up for this by -being much faster (and those rare oversights will still be caught by the CI after you open a pull -request). +potential bugs and other common issues in Python. The third tool is the linter +[ruff](https://github.com/charliermarsh/ruff), which has been recently +introduced into Qiskit Terra on an experimental basis. Only a very small number +of rules are enabled. + +You can check that your local modifications conform to the style rules by +running `tox -elint` which will run `black`, `ruff`, and `pylint` to check the +local code formatting and lint. If black returns a code formatting error you can +run `tox -eblack` to automatically update the code formatting to conform to the +style. However, if `ruff` or `pylint` return any error you will have to fix +these issues by manually updating your code. + +Because `pylint` analysis can be slow, there is also a `tox -elint-incr` target, +which runs `black` and `ruff` just as `tox -elint` does, but only applies +`pylint` to files which have changed from the source github. On rare occasions +this will miss some issues that would have been caught by checking the complete +source tree, but makes up for this by being much faster (and those rare +oversights will still be caught by the CI after you open a pull request). + +Because they are so fast, it is sometimes convenient to run the tools `black` and `ruff` separately +rather than via `tox`. If you have installed the development packages in your python environment via +`pip install -r requirements-dev.txt`, then `ruff` and `black` will be available and can be run from +the command line. See [`tox.ini`](tox.ini) for how `tox` invokes them. ## Development Cycle diff --git a/Cargo.lock b/Cargo.lock index f5528c202c0d..a0573c59a892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -73,7 +73,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.8.0", "scopeguard", ] @@ -100,9 +100,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -163,15 +163,15 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "lock_api" @@ -185,10 +185,11 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ + "autocfg", "rawpointer", ] @@ -201,6 +202,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "ndarray" version = "0.15.6" @@ -267,9 +277,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0fee4571867d318651c24f4a570c3f18408cf95f16ccb576b3ce85496a46e" +checksum = "437213adf41bbccf4aeae535fbfcdad0f6fed241e1ae182ebe97fa1f3ce19389" dependencies = [ "libc", "ndarray", @@ -282,9 +292,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "parking_lot" @@ -337,25 +347,25 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" +checksum = "cffef52f74ec3b1a1baf295d9b8fcc3070327aefc39a6d00656b13c1d0b8885c" dependencies = [ "cfg-if", "hashbrown 0.13.2", "indexmap", "indoc", "libc", - "memoffset", + "memoffset 0.9.0", "num-bigint", "num-complex", "parking_lot", @@ -367,9 +377,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" +checksum = "713eccf888fb05f1a96eb78c0dbc51907fee42b3377272dc902eb38985f418d5" dependencies = [ "once_cell", "target-lexicon", @@ -377,9 +387,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" +checksum = "5b2ecbdcfb01cbbf56e179ce969a048fd7305a66d4cdf3303e0da09d69afe4c3" dependencies = [ "libc", "pyo3-build-config", @@ -387,9 +397,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" +checksum = "b78fdc0899f2ea781c463679b20cb08af9247febc8d052de941951024cd8aea0" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -399,9 +409,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" +checksum = "60da7b84f1227c3e2fe7593505de274dcf4c8928b4e0a1c23d551a14e4e80a0f" dependencies = [ "proc-macro2", "quote", @@ -437,9 +447,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -577,15 +587,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unindent" @@ -616,9 +626,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -631,42 +641,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/Makefile b/Makefile index e6b8f5ce23d6..bea8a880e274 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,9 @@ OS := $(shell uname -s) -.PHONY: default env lint lint-incr style black test test_randomized pytest pytest_randomized test_ci coverage coverage_erase clean +.PHONY: default ruff env lint lint-incr style black test test_randomized pytest pytest_randomized test_ci coverage coverage_erase clean -default: style lint-incr test ; +default: ruff style lint-incr test ; # Dependencies need to be installed on the Anaconda virtual environment. env: @@ -41,6 +41,9 @@ lint-incr: tools/verify_headers.py qiskit test tools examples tools/find_optional_imports.py +ruff: + ruff qiskit test tools examples setup.py + style: black --check qiskit test tools examples setup.py diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index c63620897c03..cfb70dce6869 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["cdylib"] [dependencies] rayon = "1.7" -numpy = "0.18.0" +numpy = "0.19.0" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" @@ -25,7 +25,7 @@ rustworkx-core = "0.12" # The base version of PyO3 and setting a minimum feature set (e.g. probably just 'extension-module') # can be done in the workspace and inherited once we hit Rust 1.64. [dependencies.pyo3] -version = "0.18.3" +version = "0.19.0" features = ["extension-module", "hashbrown", "indexmap", "num-complex", "num-bigint", "abi3-py38"] [dependencies.ndarray] diff --git a/crates/accelerate/src/edge_collections.rs b/crates/accelerate/src/edge_collections.rs index 103d0db5d4cf..2fd06e5116f2 100644 --- a/crates/accelerate/src/edge_collections.rs +++ b/crates/accelerate/src/edge_collections.rs @@ -17,7 +17,6 @@ use pyo3::Python; /// A simple container that contains a vector representing edges in the /// coupling map that are found to be optimal by the swap mapper. #[pyclass(module = "qiskit._accelerate.stochastic_swap")] -#[pyo3(text_signature = "(/)")] #[derive(Clone, Debug)] pub struct EdgeCollection { pub edges: Vec, @@ -32,6 +31,7 @@ impl Default for EdgeCollection { #[pymethods] impl EdgeCollection { #[new] + #[pyo3(text_signature = "(/)")] pub fn new() -> Self { EdgeCollection { edges: Vec::new() } } diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index fca477298758..d699d383a7d0 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -32,7 +32,6 @@ use hashbrown::HashMap; /// qubit index. If an edge or qubit is ideal and has no error rate, you can /// either set it to ``0.0`` explicitly or as ``NaN``. #[pyclass(mapping, module = "qiskit._accelerate.error_map")] -#[pyo3(text_signature = "(num_qubits, num_edges, /")] #[derive(Clone, Debug)] pub struct ErrorMap { pub error_map: HashMap<[usize; 2], f64>, @@ -41,6 +40,7 @@ pub struct ErrorMap { #[pymethods] impl ErrorMap { #[new] + #[pyo3(text_signature = "(/, size=None)")] fn new(size: Option) -> Self { match size { Some(size) => ErrorMap { diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index c0306dce4207..87ae47a7fb43 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -25,7 +25,6 @@ use hashbrown::HashMap; /// 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")] -#[pyo3(text_signature = "(qubit_indices, logical_qubits, physical_qubits, /)")] #[derive(Clone, Debug)] pub struct NLayout { pub logic_to_phys: Vec, @@ -43,6 +42,7 @@ impl NLayout { #[pymethods] impl NLayout { #[new] + #[pyo3(text_signature = "(qubit_indices, logical_qubits, physical_qubits, /)")] fn new( qubit_indices: HashMap, logical_qubits: usize, diff --git a/crates/accelerate/src/sabre_swap/neighbor_table.rs b/crates/accelerate/src/sabre_swap/neighbor_table.rs index 7568707da8bf..528d90a4c8cf 100644 --- a/crates/accelerate/src/sabre_swap/neighbor_table.rs +++ b/crates/accelerate/src/sabre_swap/neighbor_table.rs @@ -27,7 +27,6 @@ use rayon::prelude::*; /// and used solely to represent neighbors of each node in qiskit-terra's rust /// module. #[pyclass(module = "qiskit._accelerate.sabre_swap")] -#[pyo3(text_signature = "(/)")] #[derive(Clone, Debug)] pub struct NeighborTable { pub neighbors: Vec>, @@ -36,6 +35,7 @@ pub struct NeighborTable { #[pymethods] impl NeighborTable { #[new] + #[pyo3(text_signature = "(/, adjacency_matrix=None)")] pub fn new(adjacency_matrix: Option>) -> Self { let run_in_parallel = getenv_use_multiple_threads(); let neighbors = match adjacency_matrix { diff --git a/crates/accelerate/src/sabre_swap/sabre_dag.rs b/crates/accelerate/src/sabre_swap/sabre_dag.rs index c686520debb1..26a16f485c57 100644 --- a/crates/accelerate/src/sabre_swap/sabre_dag.rs +++ b/crates/accelerate/src/sabre_swap/sabre_dag.rs @@ -19,7 +19,6 @@ use rustworkx_core::petgraph::prelude::*; /// DAGCircuit, but the contents of the node are a tuple of DAGCircuit node ids, /// a list of qargs and a list of cargs #[pyclass(module = "qiskit._accelerate.sabre_swap")] -#[pyo3(text_signature = "(num_qubits, num_clbits, nodes, /)")] #[derive(Clone, Debug)] pub struct SabreDAG { pub dag: DiGraph<(usize, Vec), ()>, @@ -29,6 +28,7 @@ pub struct SabreDAG { #[pymethods] impl SabreDAG { #[new] + #[pyo3(text_signature = "(num_qubits, num_clbits, nodes, /)")] pub fn new( num_qubits: usize, num_clbits: usize, diff --git a/crates/qasm2/Cargo.toml b/crates/qasm2/Cargo.toml index 9d09e44659ac..91fa6472d261 100644 --- a/crates/qasm2/Cargo.toml +++ b/crates/qasm2/Cargo.toml @@ -13,4 +13,4 @@ crate-type = ["cdylib"] [dependencies] hashbrown = "0.13.2" -pyo3 = { version = "0.18.3", features = ["extension-module", "abi3-py38"] } +pyo3 = { version = "0.19.0", features = ["extension-module", "abi3-py38"] } diff --git a/crates/qasm2/src/lib.rs b/crates/qasm2/src/lib.rs index fab26c5b8e93..b71a30ef96a7 100644 --- a/crates/qasm2/src/lib.rs +++ b/crates/qasm2/src/lib.rs @@ -51,7 +51,7 @@ impl CustomInstruction { /// The given `callable` must be a Python function that takes `num_params` floats, and returns a /// float. The `name` is the identifier that refers to it in the OpenQASM 2 program. This cannot /// clash with any defined gates. -#[pyclass(text_signature = "(name, num_params, callable, /)")] +#[pyclass()] #[derive(Clone)] pub struct CustomClassical { pub name: String, @@ -62,6 +62,7 @@ pub struct CustomClassical { #[pymethods] impl CustomClassical { #[new] + #[pyo3(text_signature = "(name, num_params, callable, /)")] fn __new__(name: String, num_params: usize, callable: PyObject) -> Self { Self { name, diff --git a/docs/explanation/endianness.rst b/docs/explanation/endianness.rst new file mode 100644 index 000000000000..ee78ce4d17e0 --- /dev/null +++ b/docs/explanation/endianness.rst @@ -0,0 +1,47 @@ +######################### +Order of qubits in Qiskit +######################### + +While most physics textbooks represent an :math:`n`-qubit system as the tensor product :math:`Q_0\otimes Q_1 \otimes ... \otimes Q_{n-1}`, where :math:`Q_j` is the :math:`j^{\mathrm{th}}` qubit, Qiskit uses the inverse order, that is, :math:`Q_{n-1}\otimes ... \otimes Q_1 \otimes Q_{0}`. As explained in `this video `_ from `Qiskit's YouTube channel `_, this is done to follow the convention in classical computing, in which the :math:`n^{\mathrm{th}}` bit or most significant bit (MSB) is placed on the left (with index 0) while the least significant bit (LSB) is placed on the right (index :math:`n-1`). This ordering convention is called little-endian while the one from the physics textbooks is called big-endian. + +This means that if we have, for example, a 3-qubit system with qubit 0 in state :math:`|1\rangle` and qubits 1 and 2 in state :math:`|0\rangle`, Qiskit would represent this state as :math:`|001\rangle` while most physics textbooks would represent this state as :math:`|100\rangle`. + +The matrix representation of any multi-qubit gate is also affected by this different qubit ordering. For example, if we consider the single-qubit gate + +.. math:: + + U = \begin{pmatrix} u_{00} & u_{01} \\ u_{10} & u_{11} \end{pmatrix} + +And we want a controlled version :math:`C_U` whose control qubit is qubit 0 and whose target is qubit 1, following Qiskit's ordering its matrix representation would be + +.. math:: + + C_U = \begin{pmatrix} 1 & 0 & 0 & 0 \\0 & u_{00} & 0 & u_{01} \\ 0 & 0 & 1 & 0 \\ 0 & u_{10} & 0& u_{11} \end{pmatrix} + +while in a physics textbook it would be written as + +.. math:: + + C_U = \begin{pmatrix} 1 & 0 & 0 & 0 \\0 & 1 & 0 & 0 \\ 0 & 0 & u_{00} & u_{01} \\ 0 & 0 & u_{00} & u_{01} \end{pmatrix} + + +For more details about how this ordering of MSB and LSB affects the matrix representation of any particular gate, check its entry in the circuit :mod:`~qiskit.circuit.library`. + +This different order can also make the circuit corresponding to an algorithm from a textbook a bit more complicated to visualize. Fortunately, Qiskit provides a way to represent a :class:`~.QuantumCircuit` with the most significant qubits on top, just like in the textbooks. This can be done by setting the ``reverse_bits`` argument of the :meth:`~.QuantumCircuit.draw` method to ``True``. + +Let's try this for a 3-qubit Quantum Fourier Transform (:class:`~.QFT`). + +.. plot:: + :include-source: + :context: + + from qiskit.circuit.library import QFT + + qft = QFT(3) + qft.decompose().draw('mpl') + +.. plot:: + :include-source: + :context: close-figs + + qft.decompose().draw('mpl', reverse_bits=True) \ No newline at end of file diff --git a/docs/explanation/index.rst b/docs/explanation/index.rst new file mode 100644 index 000000000000..4a2d55c82ccb --- /dev/null +++ b/docs/explanation/index.rst @@ -0,0 +1,12 @@ +.. _explanation: + +########### +Explanation +########### + + + +.. toctree:: + :maxdepth: 1 + + Order of qubits in Qiskit \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 135d5364eee6..903fc64030c0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Qiskit Terra documentation How-to Guides API References + Explanation Migration Guides Release Notes diff --git a/docs/migration_guides/algorithms_migration.rst b/docs/migration_guides/algorithms_migration.rst index 033c662cc547..b7b7ccb11380 100644 --- a/docs/migration_guides/algorithms_migration.rst +++ b/docs/migration_guides/algorithms_migration.rst @@ -74,15 +74,17 @@ How to choose a primitive configuration for your algorithm *Back to* `TL;DR`_ -The classes in :mod:`qiskit.algorithms` state the base class primitive type (``Sampler``/``Estimator``) -they require for their initialization. Once the primitive type is known, you can choose between -four different primitive implementations, depending on how you want to configure your execution: +The classes in +:mod:`qiskit.algorithms` are initialized with any implementation of :class:`qiskit.primitive.BaseSampler` or class:`qiskit.primitive.BaseEstimator`. - a. Using **local** statevector simulators for quick prototyping: **Reference Primitives** in :mod:`qiskit.primitives` - b. Using **local** Aer simulators for finer algorithm tuning: **Aer Primitives** in :mod:`qiskit_aer.primitives` - c. Accessing backends using the **Qiskit Runtime Service**: **Runtime Primitives** in :mod:`qiskit_ibm_runtime` - d. Accessing backends using a **non-Runtime-enabled provider**: **Backend Primitives** in :mod:`qiskit.primitives` +Once the kind of primitive is known, you can choose between the primitive implementations that better adjust to your case. For example: + a. For quick prototyping, you can use the **reference implementations of primitives** included in Qiskit: :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator`. + b. For finer algorithm tuning, a local simulator such as the **primitive implementation in Aer**: :class:`qiskit_aer.primitives.Sampler` and :class:`qiskit_aer.primitives.Estimator`. + c. For executing in quantum hardware you can: + + * access services with native primitive implementations, such as **IBM's Qiskit Runtime service** via :class:`qiskit_ibm_runtime.Sampler` and :class:`qiskit_ibm_runtime.Estimator` + * Wrap any backend with **Backend Primitives** (:class:`~qiskit.primitives.BackendSampler` and :class:`~qiskit.primitives.BackendEstimator`). These wrappers implement a primitive interface on top of a backend that only supports ``Backend.run()``. For more detailed information and examples, particularly on the use of the **Backend Primitives**, please refer to the `Quantum Instance migration guide `_. @@ -133,7 +135,7 @@ In this guide, we will cover 3 different common configurations for algorithms th from qiskit_aer.primitives import Sampler, Estimator - - Runtime Primitives with default configuration (see `VQD`_ example): + - IBM's Qiskit Runtime Primitives with default configuration (see `VQD`_ example): .. code-block:: python @@ -249,7 +251,7 @@ The legacy :class:`qiskit.algorithms.minimum_eigen_solvers.VQE` class has now be .. testcode:: - from qiskit.algorithms.minimum_eigensolvers import VQE # new import!!! + from qiskit.algorithms.minimum_eigensolvers import VQE # new import!!! from qiskit.algorithms.optimizers import SPSA from qiskit.circuit.library import TwoLocal from qiskit.quantum_info import SparsePauliOp diff --git a/docs/migration_guides/opflow_migration.rst b/docs/migration_guides/opflow_migration.rst index c6b899dae5cd..34554934a9cd 100644 --- a/docs/migration_guides/opflow_migration.rst +++ b/docs/migration_guides/opflow_migration.rst @@ -21,11 +21,8 @@ the :mod:`~qiskit.opflow` module to the :mod:`~qiskit.primitives` and :mod:`~qis .. attention:: Most references to the :class:`qiskit.primitives.Sampler` or :class:`qiskit.primitives.Estimator` in this guide - can be replaced with instances of the: - - - Aer primitives (:class:`qiskit_aer.primitives.Sampler`, :class:`qiskit_aer.primitives.Estimator`) - - Runtime primitives (:class:`qiskit_ibm_runtime.Sampler`, :class:`qiskit_ibm_runtime.Estimator`) - - Terra backend primitives (:class:`qiskit.primitives.BackendSampler`, :class:`qiskit.primitives.BackendEstimator`) + can be replaced with instances of any primitive implementation. For example Aer primitives (:class:`qiskit_aer.primitives.Sampler`/:class:`qiskit_aer.primitives.Estimator`) or IBM's Qiskit Runtime primitives (:class:`qiskit_ibm_runtime.Sampler`/:class:`qiskit_ibm_runtime.Estimator`). + Specific backends can be wrapped with (:class:`qiskit.primitives.BackendSampler`, :class:`qiskit.primitives.BackendEstimator`) to also present primitive-compatible interfaces. Certain classes, such as the :class:`~qiskit.opflow.expectations.AerPauliExpectation`, can only be replaced by a specific primitive instance diff --git a/docs/migration_guides/qi_migration.rst b/docs/migration_guides/qi_migration.rst index 09708f6848c8..1f27a468518e 100644 --- a/docs/migration_guides/qi_migration.rst +++ b/docs/migration_guides/qi_migration.rst @@ -50,20 +50,23 @@ Contents The Qiskit Primitives are algorithmic abstractions that encapsulate the access to backends or simulators for an easy integration into algorithm workflows. - The current pool of primitives includes **two** different **classes** (:class:`~qiskit.primitives.Sampler` and - :class:`~qiskit.primitives.Estimator`) that can be imported from **three** different locations ( - :mod:`qiskit.primitives`, :mod:`qiskit_aer.primitives` and :mod:`qiskit_ibm_runtime` ). In addition to the - reference Sampler and Estimator, :mod:`qiskit.primitives` also contains a - :class:`~qiskit.primitives.BackendSampler` and a :class:`~qiskit.primitives.BackendEstimator` class. These are + The current pool of primitives includes **two** different types of primitives: Sampler and + Estimator. + + Qiskit provides reference implementations in :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator`. Additionally, + :class:`qiskit.primitives.BackendSampler` and a :class:`qiskit.primitives.BackendEstimator` are wrappers for ``backend.run()`` that follow the primitives interface. - This guide uses the following naming standard to refer to the primitives: + Providers can implement these primitives as subclasses of :class:`~qiskit.primitives.BaseSampler` and :class:`~qiskit.primitives.BaseEstimator` respectively. + IBM's Qiskit Runtime (:mod:`qiskit_ibm_runtime`) and Aer (:mod:`qiskit_aer.primitives`) are examples of native implementations of primitives. + + This guide uses the following naming convention: - - *Primitives* - Any Sampler/Estimator implementation - - *Reference Primitives* - The Sampler and Estimator in :mod:`qiskit.primitives` --> ``from qiskit.primitives import Sampler/Estimator`` - - *Aer Primitives* - The Sampler and Estimator in :mod:`qiskit_aer.primitives` --> ``from qiskit_aer.primitives import Sampler/Estimator`` - - *Runtime Primitives* - The Sampler and Estimator in :mod:`qiskit_ibm_runtime` --> ``from qiskit_ibm_runtime import Sampler/Estimator`` - - *Backend Primitives* - The BackendSampler and BackendEstimator in :mod:`qiskit.primitives` --> ``from qiskit import BackendSampler/BackendEstimator`` + - *Primitives* - Any Sampler/Estimator implementation using base classes :class:`qiskit.primitives.BackendSampler` and a :class:`qiskit.primitives.BackendEstimator`. + - *Reference Primitives* - :class:`qiskit.primitives.Sampler` and :class:`qiskit.primitives.Estimator` are reference implementations that come with Qiskit. + - *Aer Primitives* - The `Aer `_ primitive implementations: class:`qiskit_aer.primitives.Sampler` and :class:`qiskit_aer.primitives.Estimator`. + - *Qiskit Runtime Primitives* - IBM's Qiskit Runtime primitive implementations: class:`qiskit_ibm_runtime.Sampler` and :class:`qiskit_ibm_runtime.Estimator`. + - *Backend Primitives* - Instances of :class:`qiskit.primitives.BackendSampler` and :class:`qiskit.primitives.BackendEstimator`. These allow any backend to implement primitive interfaces For guidelines on which primitives to choose for your task, please continue reading. @@ -103,7 +106,7 @@ yourself two questions: a. Using **local** statevector simulators for quick prototyping: **Reference Primitives** b. Using **local** noisy simulations for finer algorithm tuning: **Aer Primitives** - c. Accessing **runtime-enabled backends** (or cloud simulators): **Runtime Primitives** + c. Accessing **runtime-enabled backends** (or cloud simulators): **Qiskit Runtime Primitives** d. Accessing **non runtime-enabled backends** : **Backend Primitives** Arguably, the ``Sampler`` is the closest primitive to :class:`~qiskit.utils.QuantumInstance`, as they @@ -136,7 +139,7 @@ primitives **expose a similar setting through their interface**: * - QuantumInstance - Reference Primitives - Aer Primitives - - Runtime Primitives + - Qiskit Runtime Primitives - Backend Primitives * - Select ``backend`` - No @@ -186,7 +189,7 @@ primitives **expose a similar setting through their interface**: - No -(*) For more information on error mitigation and setting options on Runtime Primitives, visit +(*) For more information on error mitigation and setting options on Qiskit Runtime Primitives, visit `this link `_. (**) For more information on Runtime sessions, visit `this how-to `_. @@ -447,12 +450,12 @@ Code examples **Using Primitives** - The Runtime Primitives offer a suite of error mitigation methods that can be easily turned on with the + The Qiskit Runtime Primitives offer a suite of error mitigation methods that can be easily turned on with the ``resilience_level`` option. These are, however, not configurable. The sampler's ``resilience_level=1`` is the closest alternative to the Quantum Instance's measurement error mitigation implementation, but this is not a 1-1 replacement. - For more information on the error mitigation options in the Runtime Primitives, you can check out the following + For more information on the error mitigation options in the Qiskit Runtime Primitives, you can check out the following `link `_. .. code-block:: python @@ -503,7 +506,7 @@ Code examples * You cannot explicitly access their transpilation routine. * The mechanism to apply custom transpilation passes to the Aer, Runtime and Backend primitives is to pre-transpile locally and set ``skip_transpilation=True`` in the corresponding primitive. - * The only primitives that currently accept a custom **bound** transpiler pass manager are the **Backend Primitives**. + * The only primitives that currently accept a custom **bound** transpiler pass manager are instances of :class:`~qiskit.primitives.BackendSampler` or :class:`~qiskit.primitives.BackendEstimator`. If a ``bound_pass_manager`` is defined, the ``skip_transpilation=True`` option will **not** skip this bound pass. .. attention:: @@ -518,7 +521,7 @@ Code examples so if the circuit ended up on more qubits it did not matter. Note that the primitives **do** handle parameter bindings, meaning that even if a ``bound_pass_manager`` is defined in a - Backend Primitive, you do not have to manually assign parameters as expected in the Quantum Instance workflow. + :class:`~qiskit.primitives.BackendSampler` or :class:`~qiskit.primitives.BackendEstimator`, you do not have to manually assign parameters as expected in the Quantum Instance workflow. The use-case that motivated the addition of the two-stage transpilation to the ``QuantumInstance`` was to allow running pulse-efficient transpilation passes with the :class:`~qiskit.opflow.CircuitSampler` class. The following diff --git a/pyproject.toml b/pyproject.toml index 57400392b35e..53b83021584a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,14 @@ environment = 'RUSTUP_TOOLCHAIN="stable"' before-all = "yum install -y wget && {package}/tools/install_rust.sh" environment = 'PATH="$PATH:$HOME/.cargo/bin" CARGO_NET_GIT_FETCH_WITH_CLI="true" RUSTUP_TOOLCHAIN="stable"' +[tool.ruff] +select = [ + "F631", + "F632", + "F634", + "F823", +] + [tool.pylint.main] extension-pkg-allow-list = [ "numpy", diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 083325efad2f..d319cabc418c 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -26,7 +26,7 @@ gates, measurements and resets, which may be conditioned on real-time classical computation. A set of quantum gates is said to be universal if any unitary transformation of the quantum data can be efficiently approximated arbitrarily well -as as sequence of gates in the set. Any quantum program can be represented by a +as a sequence of gates in the set. Any quantum program can be represented by a sequence of quantum circuits and classical near-time computation. In Qiskit, this core element is represented by the :class:`QuantumCircuit` class. diff --git a/qiskit/circuit/library/generalized_gates/rv.py b/qiskit/circuit/library/generalized_gates/rv.py index 04eb70b23e36..a65c9c25ef8a 100644 --- a/qiskit/circuit/library/generalized_gates/rv.py +++ b/qiskit/circuit/library/generalized_gates/rv.py @@ -40,8 +40,10 @@ class RVGate(Gate): \newcommand{\sinc}{\text{sinc}} R(\vec{v}) = e^{-i \vec{v}\cdot\vec{\sigma}} = \begin{pmatrix} - \cos{\th} -i v_z \sinc(\th) & -(i v_x + v_y) \sinc(\th) \\ - -(i v_x - v_y) \sinc(\th) & \cos(\th) + i v_z \sinc(\th) + \cos\left(\th\right) -i v_z \sinc\left(\th\right) + & -(i v_x + v_y) \sinc\left(\th\right) \\ + -(i v_x - v_y) \sinc\left(\th\right) + & \cos\left(\th\right) + i v_z \sinc\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/n_local/excitation_preserving.py b/qiskit/circuit/library/n_local/excitation_preserving.py index d6c3be3b356a..a474e8ceb2c4 100644 --- a/qiskit/circuit/library/n_local/excitation_preserving.py +++ b/qiskit/circuit/library/n_local/excitation_preserving.py @@ -33,8 +33,8 @@ class ExcitationPreserving(TwoLocal): \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos(\th) & -i\sin(\th) & 0 \\ - 0 & -i\sin(\th) & \cos(\th) & 0 \\ + 0 & \cos\left(\th\right) & -i\sin\left(\th\right) & 0 \\ + 0 & -i\sin\left(\th\right) & \cos\left(\th\right) & 0 \\ 0 & 0 & 0 & e^{-i\phi} \end{pmatrix} diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py index a92c8d4c7c9d..e0a0d0e805e1 100644 --- a/qiskit/circuit/library/standard_gates/global_phase.py +++ b/qiskit/circuit/library/standard_gates/global_phase.py @@ -43,7 +43,6 @@ def __init__(self, phase: ParameterValueType, label: Optional[str] = None): super().__init__("global_phase", 0, [phase], label=label) def _define(self): - q = QuantumRegister(0, "q") qc = QuantumCircuit(q, name=self.name, global_phase=self.params[0]) @@ -52,7 +51,7 @@ def _define(self): def inverse(self): r"""Return inverted GLobalPhaseGate gate. - :math:`\text{GlobalPhaseGate}(\lambda){\dagger} = \text{GlobalPhaseGate}(-\lambda)` + :math:`\text{GlobalPhaseGate}(\lambda)^{\dagger} = \text{GlobalPhaseGate}(-\lambda)` """ return GlobalPhaseGate(-self.params[0]) diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 67a3b273b3ef..de19ee5d8732 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -238,7 +238,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted CPhase gate (:math:`CPhase(\lambda){\dagger} = CPhase(-\lambda)`)""" + 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): @@ -341,5 +341,5 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted MCU1 gate (:math:`MCU1(\lambda){\dagger} = MCU1(-\lambda)`)""" + r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)""" return MCPhaseGate(-self.params[0], self.num_ctrl_qubits) diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 28ca03385efc..9f05413879c1 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -44,8 +44,8 @@ class RGate(Gate): R(\theta, \phi) = e^{-i \th \left(\cos{\phi} x + \sin{\phi} y\right)} = \begin{pmatrix} - \cos{\th} & -i e^{-i \phi} \sin{\th} \\ - -i e^{i \phi} \sin{\th} & \cos{\th} + \cos\left(\th\right) & -i e^{-i \phi} \sin\left(\th\right) \\ + -i e^{i \phi} \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 7eddd05e9510..76721fa84040 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -45,8 +45,8 @@ class RXGate(Gate): RX(\theta) = \exp\left(-i \th X\right) = \begin{pmatrix} - \cos{\th} & -i\sin{\th} \\ - -i\sin{\th} & \cos{\th} + \cos\left(\th\right) & -i\sin\left(\th\right) \\ + -i\sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ @@ -137,9 +137,9 @@ class CRXGate(ControlledGate): I \otimes |0\rangle\langle 0| + RX(\theta) \otimes |1\rangle\langle 1| = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos{\th} & 0 & -i\sin{\th} \\ + 0 & \cos\left(\th\right) & 0 & -i\sin\left(\th\right) \\ 0 & 0 & 1 & 0 \\ - 0 & -i\sin{\th} & 0 & \cos{\th} + 0 & -i\sin\left(\th\right) & 0 & \cos\left(\th\right) \end{pmatrix} .. note:: @@ -165,8 +165,8 @@ class CRXGate(ControlledGate): \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ - 0 & 0 & \cos{\th} & -i\sin{\th} \\ - 0 & 0 & -i\sin{\th} & \cos{\th} + 0 & 0 & \cos\left(\th\right) & -i\sin\left(\th\right) \\ + 0 & 0 & -i\sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 8cba2ed43252..fd561784f056 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -44,8 +44,8 @@ class RYGate(Gate): RY(\theta) = \exp\left(-i \th Y\right) = \begin{pmatrix} - \cos{\th} & -\sin{\th} \\ - \sin{\th} & \cos{\th} + \cos\left(\th\right) & -\sin\left(\th\right) \\ + \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ @@ -95,7 +95,7 @@ def control( def inverse(self): r"""Return inverted RY gate. - :math:`RY(\lambda){\dagger} = RY(-\lambda)` + :math:`RY(\lambda)^{\dagger} = RY(-\lambda)` """ return RYGate(-self.params[0]) @@ -136,9 +136,9 @@ class CRYGate(ControlledGate): I \otimes |0\rangle\langle 0| + RY(\theta) \otimes |1\rangle\langle 1| = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \cos{\th} & 0 & -\sin{\th} \\ + 0 & \cos\left(\th\right) & 0 & -\sin\left(\th\right) \\ 0 & 0 & 1 & 0 \\ - 0 & \sin{\th} & 0 & \cos{\th} + 0 & \sin\left(\th\right) & 0 & \cos\left(\th\right) \end{pmatrix} .. note:: @@ -164,8 +164,8 @@ class CRYGate(ControlledGate): \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ - 0 & 0 & \cos{\th} & -\sin{\th} \\ - 0 & 0 & \sin{\th} & \cos{\th} + 0 & 0 & \cos\left(\th\right) & -\sin\left(\th\right) \\ + 0 & 0 & \sin\left(\th\right) & \cos\left(\th\right) \end{pmatrix} """ diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index f3da059f82ff..3ae7c62a6913 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -106,7 +106,7 @@ def control( def inverse(self): r"""Return inverted RZ gate - :math:`RZ(\lambda){\dagger} = RZ(-\lambda)` + :math:`RZ(\lambda)^{\dagger} = RZ(-\lambda)` """ return RZGate(-self.params[0]) diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 4dde8058adc4..168eb2e9dac7 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -138,7 +138,7 @@ def control( return gate def inverse(self): - r"""Return inverted U1 gate (:math:`U1(\lambda){\dagger} = U1(-\lambda)`)""" + r"""Return inverted U1 gate (:math:`U1(\lambda)^{\dagger} = U1(-\lambda)`)""" return U1Gate(-self.params[0]) def __array__(self, dtype=None): @@ -256,7 +256,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted CU1 gate (:math:`CU1(\lambda){\dagger} = CU1(-\lambda)`)""" + r"""Return inverted CU1 gate (:math:`CU1(\lambda)^{\dagger} = CU1(-\lambda)`)""" return CU1Gate(-self.params[0], ctrl_state=self.ctrl_state) def __array__(self, dtype=None): @@ -365,5 +365,5 @@ def control( return gate def inverse(self): - r"""Return inverted MCU1 gate (:math:`MCU1(\lambda){\dagger} = MCU1(-\lambda)`)""" + r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)""" return MCU1Gate(-self.params[0], self.num_ctrl_qubits) diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index 6240a70338ad..871aa04c2e77 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -111,7 +111,7 @@ def control( return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) def inverse(self): - r"""Return inverted Y gate (:math:`Y{\dagger} = Y`)""" + r"""Return inverted Y gate (:math:`Y^{\dagger} = Y`)""" return YGate() # self-inverse def __array__(self, dtype=complex): diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 9299ae054d73..37cb1c508575 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -877,6 +877,9 @@ def compose( lcr_1: 0 ═══════════ lcr_1: 0 ═══════════════════════ """ + # pylint: disable=cyclic-import + from qiskit.circuit.controlflow.switch_case import SwitchCaseOp + if inplace and front and self._control_flow_scopes: # If we're composing onto ourselves while in a stateful control-flow builder context, # there's no clear meaning to composition to the "front" of the circuit. @@ -955,30 +958,42 @@ def compose( ) edge_map.update(zip(other.clbits, dest.cbit_argument_conversion(clbits))) + # Cache for `map_register_to_dest`. + _map_register_cache = {} + + def map_register_to_dest(theirs): + """Map the target's registers to suitable equivalents in the destination, adding an + extra one if there's no exact match.""" + if theirs.name in _map_register_cache: + return _map_register_cache[theirs.name] + mapped_bits = [edge_map[bit] for bit in theirs] + for ours in dest.cregs: + if mapped_bits == list(ours): + mapped_theirs = ours + break + else: + mapped_theirs = ClassicalRegister(bits=mapped_bits) + dest.add_register(mapped_theirs) + _map_register_cache[theirs.name] = mapped_theirs + return mapped_theirs + mapped_instrs: list[CircuitInstruction] = [] - condition_register_map = {} for instr in other.data: n_qargs: list[Qubit] = [edge_map[qarg] for qarg in instr.qubits] n_cargs: list[Clbit] = [edge_map[carg] for carg in instr.clbits] n_op = instr.operation.copy() - # Map their registers over to ours, adding an extra one if there's no exact match. if getattr(n_op, "condition", None) is not None: target, value = n_op.condition if isinstance(target, Clbit): n_op.condition = (edge_map[target], value) else: - if target.name not in condition_register_map: - mapped_bits = [edge_map[bit] for bit in target] - for our_creg in dest.cregs: - if mapped_bits == list(our_creg): - new_target = our_creg - break - else: - new_target = ClassicalRegister(bits=[edge_map[bit] for bit in target]) - dest.add_register(new_target) - condition_register_map[target.name] = new_target - n_op.condition = (condition_register_map[target.name], value) + n_op.condition = (map_register_to_dest(target), value) + elif isinstance(n_op, SwitchCaseOp): + if isinstance(n_op.target, Clbit): + n_op.target = edge_map[n_op.target] + else: + n_op.target = map_register_to_dest(n_op.target) mapped_instrs.append(CircuitInstruction(n_op, n_qargs, n_cargs)) @@ -2839,7 +2854,17 @@ def _assign_parameter(self, parameter: Parameter, value: ParameterValueType) -> new_param = assignee.assign(parameter, value) # if fully bound, validate if len(new_param.parameters) == 0: - instr.params[param_index] = instr.validate_parameter(new_param) + if new_param._symbol_expr.is_integer and new_param.is_real(): + val = int(new_param) + elif new_param.is_real(): + # Workaround symengine not supporting float() + val = complex(new_param).real + else: + # complex values may no longer be supported but we + # defer raising an exception to validdate_parameter + # below for now. + val = complex(new_param) + instr.params[param_index] = instr.validate_parameter(val) else: instr.params[param_index] = new_param @@ -2896,7 +2921,11 @@ def _assign_calibration_parameters( if isinstance(p, ParameterExpression) and parameter in p.parameters: new_param = p.assign(parameter, value) if not new_param.parameters: - new_param = float(new_param) + if new_param._symbol_expr.is_integer: + new_param = int(new_param) + else: + # Workaround symengine not supporting float() + new_param = complex(new_param).real new_cal_params.append(new_param) else: new_cal_params.append(p) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index dc93c85a5fcf..706a223f8c92 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -28,7 +28,6 @@ from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import Qubit -from qiskit.converters import isinstanceint, isinstancelist from qiskit.dagcircuit import DAGCircuit from qiskit.providers.backend import Backend from qiskit.providers.models import BackendProperties @@ -757,16 +756,27 @@ def _parse_initial_layout(initial_layout, circuits): def _layout_from_raw(initial_layout, circuit): if initial_layout is None or isinstance(initial_layout, Layout): return initial_layout - elif isinstancelist(initial_layout): - if all(isinstanceint(elem) for elem in initial_layout): - initial_layout = Layout.from_intlist(initial_layout, *circuit.qregs) - elif all(elem is None or isinstance(elem, Qubit) for elem in initial_layout): - initial_layout = Layout.from_qubit_list(initial_layout, *circuit.qregs) - elif isinstance(initial_layout, dict): - initial_layout = Layout(initial_layout) + if isinstance(initial_layout, dict): + return Layout(initial_layout) + # Should be an iterable either of ints or bits/None. + specifier = tuple(initial_layout) + if all(phys is None or isinstance(phys, Qubit) for phys in specifier): + mapping = {phys: virt for phys, virt in enumerate(specifier) if virt is not None} + if len(mapping) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(mapping)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) else: - raise TranspilerError("The initial_layout parameter could not be parsed") - return initial_layout + if len(specifier) != circuit.num_qubits: + raise TranspilerError( + f"'initial_layout' ({len(specifier)}) and circuit ({circuit.num_qubits}) had" + " different numbers of qubits" + ) + if len(specifier) != len(set(specifier)): + raise TranspilerError(f"'initial_layout' contained duplicate entries: {specifier}") + mapping = {int(phys): virt for phys, virt in zip(specifier, circuit.qubits)} + return Layout(mapping) # multiple layouts? if isinstance(initial_layout, list) and any( diff --git a/qiskit/dagcircuit/collect_blocks.py b/qiskit/dagcircuit/collect_blocks.py index ed4df4c38b30..3c09d5dcb82b 100644 --- a/qiskit/dagcircuit/collect_blocks.py +++ b/qiskit/dagcircuit/collect_blocks.py @@ -14,7 +14,8 @@ """Various ways to divide a DAG into blocks of nodes, to split blocks of nodes into smaller sub-blocks, and to consolidate blocks.""" -from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit.circuit import QuantumCircuit, CircuitInstruction, ClassicalRegister +from qiskit.circuit.controlflow.condition import condition_bits from . import DAGOpNode, DAGCircuit, DAGDependency from .exceptions import DAGCircuitError @@ -254,22 +255,46 @@ def collapse_to_operation(self, blocks, collapse_fn): then uses collapse_fn to collapse this circuit into a single operation. """ global_index_map = {wire: idx for idx, wire in enumerate(self.dag.qubits)} + global_index_map.update({wire: idx for idx, wire in enumerate(self.dag.clbits)}) + for block in blocks: - # Find the set of qubits used in this block (which might be much smaller than - # the set of all qubits). + # Find the sets of qubits/clbits used in this block (which might be much smaller + # than the set of all qubits/clbits). cur_qubits = set() + cur_clbits = set() + + # Additionally, find the set of classical registers used in conditions over full registers + # (in such a case, we need to add that register to the block circuit, not just its clbits). + cur_clregs = [] + for node in block: cur_qubits.update(node.qargs) - - # For reproducibility, order these qubits compatibly with the global order. + cur_clbits.update(node.cargs) + cond = getattr(node.op, "condition", None) + if cond is not None: + cur_clbits.update(condition_bits(cond)) + if isinstance(cond[0], ClassicalRegister): + cur_clregs.append(cond[0]) + + # For reproducibility, order these qubits/clbits compatibly with the global order. sorted_qubits = sorted(cur_qubits, key=lambda x: global_index_map[x]) + sorted_clbits = sorted(cur_clbits, key=lambda x: global_index_map[x]) + + qc = QuantumCircuit(sorted_qubits, sorted_clbits) + + # Add classical registers used in conditions over registers + for reg in cur_clregs: + qc.add_register(reg) # Construct a quantum circuit from the nodes in the block, remapping the qubits. wire_pos_map = {qb: ix for ix, qb in enumerate(sorted_qubits)} - qc = QuantumCircuit(len(cur_qubits)) + wire_pos_map.update({qb: ix for ix, qb in enumerate(sorted_clbits)}) + for node in block: - remapped_qubits = [wire_pos_map[qarg] for qarg in node.qargs] - qc.append(CircuitInstruction(node.op, remapped_qubits, node.cargs)) + instructions = qc.append(CircuitInstruction(node.op, node.qargs, node.cargs)) + cond = getattr(node.op, "condition", None) + if cond is not None: + instructions.c_if(*cond) # Collapse this quantum circuit into an operation. op = collapse_fn(qc) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index fbc60591d1f7..f40015d7f078 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -30,6 +30,7 @@ import rustworkx as rx from qiskit.circuit import ControlFlowOp, ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp +from qiskit.circuit.controlflow.condition import condition_bits from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit @@ -1129,8 +1130,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): for nd in node_block: block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): - block_cargs |= set(nd.cargs) + block_cargs |= set(nd.cargs) + cond = getattr(nd.op, "condition", None) + if cond is not None: + block_cargs.update(condition_bits(cond)) # Create replacement node new_node = DAGOpNode( diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 1ed5eb96e113..128129e76372 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -19,6 +19,7 @@ import rustworkx as rx +from qiskit.circuit.controlflow.condition import condition_bits from qiskit.circuit.quantumregister import QuantumRegister, Qubit from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.dagcircuit.exceptions import DAGDependencyError @@ -394,11 +395,8 @@ def _create_op_node(self, operation, qargs, cargs): # (1) cindices_list are specific to template optimization and should not be computed # in this place. # (2) Template optimization pass needs currently does not handle general conditions. - if isinstance(operation.condition[0], Clbit): - condition_bits = [operation.condition[0]] - else: - condition_bits = operation.condition[0] - cindices_list = [self.clbits.index(clbit) for clbit in condition_bits] + cond_bits = condition_bits(operation.condition) + cindices_list = [self.clbits.index(clbit) for clbit in cond_bits] else: cindices_list = [] else: @@ -591,8 +589,10 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True): for nd in node_block: block_qargs |= set(nd.qargs) - if nd.op.condition: - block_cargs |= set(nd.cargs) + block_cargs |= set(nd.cargs) + cond = getattr(nd.op, "condition", None) + if cond is not None: + block_cargs.update(condition_bits(cond)) # Create replacement node new_node = self._create_op_node( diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 2718792ad153..878f6a4741f5 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -1450,7 +1450,7 @@ def Sin( .. math:: - f(x) &= \\text{A}\\sin\\left(2\\pi\text{freq}x+\\text{phase}\\right) , 0 <= x < duration + f(x) = \\text{A}\\sin\\left(2\\pi\\text{freq}x+\\text{phase}\\right) , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`. @@ -1517,7 +1517,7 @@ def Cos( .. math:: - f(x) &= \\text{A}\\cos\\left(2\\pi\text{freq}x+\\text{phase}\\right) , 0 <= x < duration + f(x) = \\text{A}\\cos\\left(2\\pi\\text{freq}x+\\text{phase}\\right) , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`. @@ -1584,7 +1584,7 @@ def Sawtooth( .. math:: - f(x) &= 2\\text{A}\\left[g\\left(x\\right)- + f(x) = 2\\text{A}\\left[g\\left(x\\right)- \\lfloor g\\left(x\\right)+\\frac{1}{2}\\rfloor\\right] where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`, @@ -1655,7 +1655,7 @@ def Triangle( .. math:: - f(x) &= \\text{A}\\left[\\text{sawtooth}\\left(x\\right)right] , 0 <= x < duration + f(x) = \\text{A}\\left[\\text{sawtooth}\\left(x\\right)\\right] , 0 <= x < duration where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`, and :math:`\\text{sawtooth}\\left(x\\right)` is a sawtooth wave with the same frequency diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index e4555c7fba04..4dbce78d282a 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -31,7 +31,7 @@ TwoQubitWeylDecomposition, ) from qiskit.quantum_info import Operator -from qiskit.circuit import ControlFlowOp, Gate +from qiskit.circuit import ControlFlowOp, Gate, Parameter from qiskit.circuit.library.standard_gates import ( iSwapGate, CXGate, @@ -673,13 +673,24 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree): # available instructions on this qubit pair, and their associated property. available_2q_basis = {} available_2q_props = {} + + # 2q gates sent to 2q decomposers must not have any symbolic parameters. The + # gates must be convertable to a numeric matrix. If a basis gate supports an arbitrary + # angle, we have to choose one angle (or more.) + def _replace_parameterized_gate(op): + if isinstance(op, RXXGate) and isinstance(op.params[0], Parameter): + op = RXXGate(pi / 2) + elif isinstance(op, RZXGate) and isinstance(op.params[0], Parameter): + op = RZXGate(pi / 4) + return op + try: keys = target.operation_names_for_qargs(qubits_tuple) for key in keys: op = target.operation_from_name(key) if not isinstance(op, Gate): continue - available_2q_basis[key] = op + available_2q_basis[key] = _replace_parameterized_gate(op) available_2q_props[key] = target[key][qubits_tuple] except KeyError: pass @@ -690,7 +701,7 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree): op = target.operation_from_name(key) if not isinstance(op, Gate): continue - available_2q_basis[key] = op + available_2q_basis[key] = _replace_parameterized_gate(op) available_2q_props[key] = target[key][reverse_tuple] except KeyError: pass diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 5c1720ccb1d6..ae1f43b3c243 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -144,7 +144,9 @@ def from_backend(cls, backend, **pass_manager_options): res.inst_map = backend.instruction_schedule_map if res.coupling_map is None: if backend_version < 2: - res.coupling_map = CouplingMap(getattr(config, "coupling_map", None)) + cmap_edge_list = getattr(config, "coupling_map", None) + if cmap_edge_list is not None: + res.coupling_map = CouplingMap(cmap_edge_list) else: res.coupling_map = backend.coupling_map if res.instruction_durations is None: diff --git a/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml b/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml new file mode 100644 index 000000000000..036d672e480d --- /dev/null +++ b/releasenotes/notes/circuit-assign-parameter-to-concrete-value-7cad75c97183257f.yaml @@ -0,0 +1,29 @@ +--- +fixes: + - | + Changed the binding of numeric values with + :meth:`.QuantumCircuit.assign_parameters` to avoid a mismatch between the + values of circuit instruction parameters and corresponding parameter keys + in the circuit's calibration dictionary. Fixed `#9764 + `_ and `#10166 + `_. See also the + related upgrade note regarding :meth:`.QuantumCircuit.assign_parameters`. +upgrade: + - | + Changed :meth:`.QuantumCircuit.assign_parameters` to bind + assigned integer and float values directly into the parameters of + :class:`~qiskit.circuit.Instruction` instances in the circuit rather than + binding the values wrapped within a + :class:`~qiskit.circuit.ParameterExpression`. This change should have + little user impact as ``float(QuantumCircuit.data[i].operation.params[j])`` + still produces a ``float`` (and is the only way to access the value of a + :class:`~qiskit.circuit.ParameterExpression`). Also, + :meth:`~qiskit.circuit.Instruction` parameters could already be ``float`` + as well as a :class:`~qiskit.circuit.ParameterExpression`, so code dealing + with instruction parameters should already handle both cases. The most + likely chance for user impact is in code that uses ``isinstance`` to check + for :class:`~qiskit.circuit.ParameterExpression` and behaves differently + depending on the result. Additionally, qpy serializes the numeric value in + a bound :class:`~qiskit.circuit.ParameterExpression` at a different + precision than a ``float`` (see also the related bug fix note about + :meth:`.QuantumCircuit.assign_parameters`). diff --git a/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml b/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml new file mode 100644 index 000000000000..ef3e073e28af --- /dev/null +++ b/releasenotes/notes/fix-collapse-with-clbits-e14766353303d442.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed a bug in :class:`~BlockCollapser` where classical bits were ignored when collapsing + a block of nodes. + - | + Fixed a bug in :meth:`~qiskit.dagcircuit.DAGCircuit.replace_block_with_op` and + :meth:`~qiskit.dagcircuit.DAGDependency.replace_block_with_op` + that led to ignoring classical bits. diff --git a/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml b/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml new file mode 100644 index 000000000000..da83aafe33c7 --- /dev/null +++ b/releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a bug in :meth:`.QuantumCircuit.compose` where the :attr:`.SwitchCaseOp.target` attribute + in the subcircuit would not get mapped to a register in the base circuit correctly. diff --git a/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml new file mode 100644 index 000000000000..4bac5214f5c6 --- /dev/null +++ b/releasenotes/notes/fix-initial_layout-loose-qubits-0c59b2d6fb99d7e6.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Using ``initial_layout`` in calls to :func:`.transpile` will no longer error if the + circuit contains qubits not in any registers, or qubits that exist in more than one + register. See `#10125 `__. diff --git a/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml b/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml new file mode 100644 index 000000000000..c85355478f06 --- /dev/null +++ b/releasenotes/notes/fix-pm-config-from-backend-f3b71b11858b4f08.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue with the :meth:`.PassManagerConfig.from_backend` constructor + when building a :class:`~.PassManagerConfig` object from a :class:`~.BackendV1` + instance that didn't have a coupling map attribute defined. Previously, the + constructor would incorrectly create a :class:`~.CouplingMap` object with + 0 qubits instead of using ``None``. + Fixed `#10171 `__ diff --git a/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml b/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml new file mode 100644 index 000000000000..a58af12280b2 --- /dev/null +++ b/releasenotes/notes/fix-synth-fail-with-symbolic-angles-a070b9973a16b8c3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes a bug introduced in Qiskit 0.24.0 where numeric rotation angles were no longer substituted + for symbolic ones before preparing for two-qubit synthesis. This caused an exception to be + raised because the synthesis routines require numberic matrices. diff --git a/requirements-dev.txt b/requirements-dev.txt index cf0990e6a36a..fe67acb6ef98 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,6 +11,7 @@ black[jupyter]~=22.0 pydot astroid==2.14.2 pylint==2.16.2 +ruff==0.0.267 stestr>=2.0.0,!=4.0.0 pylatexenc>=1.4 ddt>=1.2.0,!=1.4.0,!=1.4.3 diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 3bedc5cb9da0..331c30471032 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -19,7 +19,7 @@ import numpy as np -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, pulse from qiskit.circuit import CASE_DEFAULT from qiskit.circuit.classicalregister import Clbit from qiskit.circuit.quantumregister import Qubit @@ -274,6 +274,39 @@ def test_bound_parameter(self): self.assertEqual(qc, new_circ) self.assertDeprecatedBitProperties(qc, new_circ) + def test_bound_calibration_parameter(self): + """Test a circuit with a bound calibration parameter is correctly serialized. + + In particular, this test ensures that parameters on a circuit + instruction are consistent with the circuit's calibrations dictionary + after serialization. + """ + amp = Parameter("amp") + + with pulse.builder.build() as sched: + pulse.builder.play(pulse.Constant(100, amp), pulse.DriveChannel(0)) + + gate = Gate("custom", 1, [amp]) + + qc = QuantumCircuit(1) + qc.append(gate, (0,)) + qc.add_calibration(gate, (0,), sched) + qc.assign_parameters({amp: 1 / 3}, inplace=True) + + qpy_file = io.BytesIO() + dump(qc, qpy_file) + qpy_file.seek(0) + new_circ = load(qpy_file)[0] + self.assertEqual(qc, new_circ) + instruction = new_circ.data[0] + cal_key = ( + tuple(new_circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + # Make sure that looking for a calibration based on the instruction's + # parameters succeeds + self.assertIn(cal_key, new_circ.calibrations[gate.name]) + def test_parameter_expression(self): """Test a circuit with a parameter expression.""" theta = Parameter("theta") diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 3fc745a9a705..0f77493361fb 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -29,6 +29,8 @@ Parameter, Gate, Instruction, + CASE_DEFAULT, + SwitchCaseOp, ) from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal from qiskit.test import QiskitTestCase @@ -477,6 +479,45 @@ def test_compose_conditional_no_match(self): self.assertEqual(z.condition[1], 1) self.assertIs(x.condition[0][0], test.clbits[1]) + def test_compose_switch_match(self): + """Test that composition containing a `switch` with a register that matches proceeds + correctly.""" + case_0 = QuantumCircuit(1, 2) + case_0.x(0) + case_1 = QuantumCircuit(1, 2) + case_1.z(0) + case_default = QuantumCircuit(1, 2) + cr = ClassicalRegister(2, "target") + right = QuantumCircuit(QuantumRegister(1), cr) + right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1]) + + test = QuantumCircuit(QuantumRegister(3), cr, ClassicalRegister(2)).compose( + right, [1], [0, 1] + ) + + expected = test.copy_empty_like() + expected.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [1], [0, 1]) + self.assertEqual(test, expected) + + def test_compose_switch_no_match(self): + """Test that composition containing a `switch` with a register that matches proceeds + correctly.""" + case_0 = QuantumCircuit(1, 2) + case_0.x(0) + case_1 = QuantumCircuit(1, 2) + case_1.z(0) + case_default = QuantumCircuit(1, 2) + cr = ClassicalRegister(2, "target") + right = QuantumCircuit(QuantumRegister(1), cr) + right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1]) + test = QuantumCircuit(3, 3).compose(right, [1], [0, 1]) + + self.assertEqual(len(test.data), 1) + self.assertIsInstance(test.data[0].operation, SwitchCaseOp) + target = test.data[0].operation.target + self.assertIn(target, test.cregs) + self.assertEqual(list(target), test.clbits[0:2]) + def test_compose_gate(self): """Composing with a gate. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index e19fbd205166..d610a3353067 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -21,7 +21,7 @@ from test import combine import numpy -from ddt import data, ddt +from ddt import data, ddt, named_data import qiskit import qiskit.circuit.library as circlib @@ -346,6 +346,23 @@ def test_multiple_named_parameters(self): self.assertEqual(theta.name, "θ") self.assertEqual(qc.parameters, {theta, x}) + @named_data( + ["int", 2, int], + ["float", 2.5, float], + ["float16", numpy.float16(2.5), float], + ["float32", numpy.float32(2.5), float], + ["float64", numpy.float64(2.5), float], + ) + def test_circuit_assignment_to_numeric(self, value, type_): + """Test binding a numeric value to a circuit instruction""" + x = Parameter("x") + qc = QuantumCircuit(1) + qc.append(Instruction("inst", 1, 0, [x]), (0,)) + qc.assign_parameters({x: value}, inplace=True) + bound = qc.data[0].operation.params[0] + self.assertIsInstance(bound, type_) + self.assertEqual(bound, value) + def test_partial_binding(self): """Test that binding a subset of circuit parameters returns a new parameterized circuit.""" theta = Parameter("θ") @@ -401,10 +418,10 @@ def test_expression_partial_binding(self): self.assertTrue(isinstance(pqc.data[0].operation.params[0], ParameterExpression)) self.assertEqual(str(pqc.data[0].operation.params[0]), "phi + 2") - fbqc = getattr(pqc, assign_fun)({phi: 1}) + fbqc = getattr(pqc, assign_fun)({phi: 1.0}) self.assertEqual(fbqc.parameters, set()) - self.assertTrue(isinstance(fbqc.data[0].operation.params[0], ParameterExpression)) + self.assertIsInstance(fbqc.data[0].operation.params[0], float) self.assertEqual(float(fbqc.data[0].operation.params[0]), 3) def test_two_parameter_expression_binding(self): @@ -448,7 +465,7 @@ def test_expression_partial_binding_zero(self): fbqc = getattr(pqc, assign_fun)({phi: 1}) self.assertEqual(fbqc.parameters, set()) - self.assertTrue(isinstance(fbqc.data[0].operation.params[0], ParameterExpression)) + 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): @@ -505,8 +522,15 @@ def test_calibration_assignment(self): circ.add_calibration("rxt", [0], rxt_q0, [theta]) circ = circ.assign_parameters({theta: 3.14}) - self.assertTrue(((0,), (3.14,)) in circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][((0,), (3.14,))] + instruction = circ.data[0] + cal_key = ( + tuple(circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + self.assertEqual(cal_key, ((0,), (3.14,))) + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) def test_calibration_assignment_doesnt_mutate(self): @@ -531,11 +555,11 @@ def test_calibration_assignment_doesnt_mutate(self): self.assertNotEqual(assigned_circ.calibrations, circ.calibrations) def test_calibration_assignment_w_expressions(self): - """That calibrations with multiple parameters and more expressions.""" + """That calibrations with multiple parameters are assigned correctly""" theta = Parameter("theta") sigma = Parameter("sigma") circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [theta, sigma]), [0]) + circ.append(Gate("rxt", 1, [theta / 2, sigma]), [0]) circ.measure(0, 0) rxt_q0 = pulse.Schedule( @@ -548,8 +572,15 @@ def test_calibration_assignment_w_expressions(self): circ.add_calibration("rxt", [0], rxt_q0, [theta / 2, sigma]) circ = circ.assign_parameters({theta: 3.14, sigma: 4}) - self.assertTrue(((0,), (3.14 / 2, 4)) in circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][((0,), (3.14 / 2, 4))] + instruction = circ.data[0] + cal_key = ( + tuple(circ.find_bit(q).index for q in instruction.qubits), + tuple(instruction.operation.params), + ) + self.assertEqual(cal_key, ((0,), (3.14 / 2, 4))) + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) self.assertEqual(sched.instructions[0][1].pulse.sigma, 16) @@ -789,7 +820,7 @@ def test_binding_parameterized_circuits_built_in_multiproc(self): for qc in results: circuit.compose(qc, inplace=True) - parameter_values = [{x: 1 for x in parameters}] + parameter_values = [{x: 1.0 for x in parameters}] qobj = assemble( circuit, @@ -802,7 +833,7 @@ def test_binding_parameterized_circuits_built_in_multiproc(self): self.assertTrue( all( len(inst.params) == 1 - and isinstance(inst.params[0], ParameterExpression) + and isinstance(inst.params[0], float) and float(inst.params[0]) == 1 for inst in qobj.experiments[0].instructions ) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index fe5670aba861..c25425d051f1 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -627,14 +627,9 @@ def test_wrong_initial_layout(self): QuantumRegister(3, "q")[2], ] - with self.assertRaises(TranspilerError) as cm: + with self.assertRaisesRegex(TranspilerError, "different numbers of qubits"): transpile(qc, backend, initial_layout=bad_initial_layout) - self.assertEqual( - "FullAncillaAllocation: The layout refers to a qubit that does not exist in circuit.", - cm.exception.message, - ) - def test_parameterized_circuit_for_simulator(self): """Verify that a parameterized circuit can be transpiled for a simulator backend.""" qr = QuantumRegister(2, name="qr") @@ -1616,6 +1611,30 @@ def test_transpile_identity_circuit_no_target(self, opt_level): result = transpile(qc, optimization_level=opt_level) self.assertEqual(empty_qc, result) + @data(0, 1, 2, 3) + def test_initial_layout_with_loose_qubits(self, opt_level): + """Regression test of gh-10125.""" + qc = QuantumCircuit([Qubit(), Qubit()]) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + + @data(0, 1, 2, 3) + def test_initial_layout_with_overlapping_qubits(self, opt_level): + """Regression test of gh-10125.""" + qr1 = QuantumRegister(2, "qr1") + qr2 = QuantumRegister(bits=qr1[:]) + qc = QuantumCircuit(qr1, qr2) + qc.cx(0, 1) + transpiled = transpile(qc, initial_layout=[1, 0], optimization_level=opt_level) + self.assertIsNotNone(transpiled.layout) + self.assertEqual( + transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]}) + ) + @ddt class TestPostTranspileIntegration(QiskitTestCase): diff --git a/test/python/dagcircuit/test_collect_blocks.py b/test/python/dagcircuit/test_collect_blocks.py index 6bfc5756d9df..fcafafa753af 100644 --- a/test/python/dagcircuit/test_collect_blocks.py +++ b/test/python/dagcircuit/test_collect_blocks.py @@ -15,10 +15,17 @@ import unittest -from qiskit.converters import circuit_to_dag, circuit_to_dagdependency +from qiskit import QuantumRegister, ClassicalRegister +from qiskit.converters import ( + circuit_to_dag, + circuit_to_dagdependency, + circuit_to_instruction, + dag_to_circuit, + dagdependency_to_circuit, +) from qiskit.test import QiskitTestCase -from qiskit.circuit import QuantumCircuit -from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter +from qiskit.circuit import QuantumCircuit, Measure, Clbit +from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockSplitter, BlockCollapser class TestCollectBlocks(QiskitTestCase): @@ -449,6 +456,317 @@ def test_do_not_split_blocks(self): ) self.assertEqual(len(blocks), 1) + def test_collect_blocks_with_cargs(self): + """Test collecting and collapsing blocks with classical bits appearing as cargs.""" + + qc = QuantumCircuit(3) + qc.h(0) + qc.h(1) + qc.h(2) + qc.measure_all() + + dag = circuit_to_dag(qc) + + # Collect all measure instructions + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of 3 measures + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + self.assertEqual(blocks[0][0].op, Measure()) + self.assertEqual(blocks[0][1].op, Measure()) + self.assertEqual(blocks[0][2].op, Measure()) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 5) + self.assertEqual(collapsed_qc.data[0].operation.name, "h") + self.assertEqual(collapsed_qc.data[1].operation.name, "h") + self.assertEqual(collapsed_qc.data[2].operation.name, "h") + self.assertEqual(collapsed_qc.data[3].operation.name, "barrier") + self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cargs_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing as cargs, + using DAGDependency.""" + + qc = QuantumCircuit(3) + qc.h(0) + qc.h(1) + qc.h(2) + qc.measure_all() + + dag = circuit_to_dagdependency(qc) + + # Collect all measure instructions + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: isinstance(node.op, Measure), split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of 3 measures + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + self.assertEqual(blocks[0][0].op, Measure()) + self.assertEqual(blocks[0][1].op, Measure()) + self.assertEqual(blocks[0][2].op, Measure()) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 5) + self.assertEqual(collapsed_qc.data[0].operation.name, "h") + self.assertEqual(collapsed_qc.data[1].operation.name, "h") + self.assertEqual(collapsed_qc.data[2].operation.name, "h") + self.assertEqual(collapsed_qc.data[3].operation.name, "barrier") + self.assertEqual(collapsed_qc.data[4].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[4].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[4].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_clbits(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition.""" + + qc = QuantumCircuit(4, 3) + qc.cx(0, 1).c_if(0, 1) + qc.cx(2, 3) + qc.cx(1, 2) + qc.cx(0, 1) + qc.cx(2, 3).c_if(1, 0) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 5) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2) + + def test_collect_blocks_with_clbits_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing + under conditions, using DAGDependency.""" + + qc = QuantumCircuit(4, 3) + qc.cx(0, 1).c_if(0, 1) + qc.cx(2, 3) + qc.cx(1, 2) + qc.cx(0, 1) + qc.cx(2, 3).c_if(1, 0) + + dag = circuit_to_dagdependency(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 5) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 2) + + def test_collect_blocks_with_clbits2(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + cbit = Clbit() + + qc = QuantumCircuit(qreg, creg, [cbit]) + qc.cx(0, 1).c_if(creg[1], 1) + qc.cx(2, 3).c_if(cbit, 0) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 4) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_clbits2_dagdependency(self): + """Test collecting and collapsing blocks with classical bits appearing under + condition, using DAGDependency.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + cbit = Clbit() + + qc = QuantumCircuit(qreg, creg, [cbit]) + qc.cx(0, 1).c_if(creg[1], 1) + qc.cx(2, 3).c_if(cbit, 0) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 4) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 4) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cregs(self): + """Test collecting and collapsing blocks with classical registers appearing under + condition.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + creg2 = ClassicalRegister(2, "cr2") + + qc = QuantumCircuit(qreg, creg, creg2) + qc.cx(0, 1).c_if(creg, 3) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dag(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dag_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + + def test_collect_blocks_with_cregs_dagdependency(self): + """Test collecting and collapsing blocks with classical registers appearing under + condition, using DAGDependency.""" + + qreg = QuantumRegister(4, "qr") + creg = ClassicalRegister(3, "cr") + creg2 = ClassicalRegister(2, "cr2") + + qc = QuantumCircuit(qreg, creg, creg2) + qc.cx(0, 1).c_if(creg, 3) + qc.cx(1, 2) + qc.cx(0, 1).c_if(creg[2], 1) + + dag = circuit_to_dagdependency(qc) + + # Collect all cx gates (including the conditional ones) + blocks = BlockCollector(dag).collect_all_matching_blocks( + lambda node: node.op.name == "cx", split_blocks=False, min_block_size=1 + ) + + # We should have a single block consisting of all CX nodes + self.assertEqual(len(blocks), 1) + self.assertEqual(len(blocks[0]), 3) + + def _collapse_fn(circuit): + op = circuit_to_instruction(circuit) + op.name = "COLLAPSED" + return op + + # Collapse block with measures into a single "COLLAPSED" block + dag = BlockCollapser(dag).collapse_to_operation(blocks, _collapse_fn) + collapsed_qc = dagdependency_to_circuit(dag) + + self.assertEqual(len(collapsed_qc.data), 1) + self.assertEqual(collapsed_qc.data[0].operation.name, "COLLAPSED") + self.assertEqual(len(collapsed_qc.data[0].operation.definition.cregs), 1) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_qubits, 3) + self.assertEqual(collapsed_qc.data[0].operation.definition.num_clbits, 3) + if __name__ == "__main__": unittest.main() diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 2f3030ca3ce6..9978426e6d9e 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -469,7 +469,7 @@ def test_reused_custom_parameter(self): " rx(0.5) _gate_q_0;", "}", f"gate {circuit_name_1} _gate_q_0 {{", - " rx(1) _gate_q_0;", + " rx(1.0) _gate_q_0;", "}", "qubit[1] _all_qubits;", "let q = _all_qubits[0:0];", diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py index d78b52e86542..96721e1884a3 100644 --- a/test/python/transpiler/test_passmanager_config.py +++ b/test/python/transpiler/test_passmanager_config.py @@ -86,6 +86,7 @@ def test_simulator_backend_v1(self): config = PassManagerConfig.from_backend(backend) self.assertIsInstance(config, PassManagerConfig) self.assertIsNone(config.inst_map) + self.assertIsNone(config.coupling_map) def test_invalid_user_option(self): """Test from_backend() with an invalid user option.""" @@ -103,7 +104,7 @@ def test_str(self): initial_layout: None basis_gates: ['id', 'rz', 'sx', 'x'] inst_map: None - coupling_map: + coupling_map: None layout_method: None routing_method: None translation_method: None diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index 39a2a5880172..4aaecbf2e1a3 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -50,6 +50,7 @@ IGate, CXGate, RZGate, + RXGate, SXGate, XGate, iSwapGate, @@ -914,6 +915,21 @@ def test_iswap_no_cx_synthesis_succeeds(self): result_qc = dag_to_circuit(result_dag) self.assertTrue(np.allclose(Operator(result_qc.to_gate()).to_matrix(), cxmat)) + def test_parameterized_basis_gate_in_target(self): + """Test synthesis with parameterized RXX gate.""" + theta = Parameter("θ") + lam = Parameter("λ") + target = Target(num_qubits=2) + target.add_instruction(RZGate(lam)) + target.add_instruction(RXGate(theta)) + target.add_instruction(RXXGate(theta)) + qc = QuantumCircuit(2) + qc.cp(np.pi / 2, 0, 1) + qc_transpiled = transpile(qc, target=target, optimization_level=3, seed_transpiler=42) + opcount = qc_transpiled.count_ops() + self.assertTrue(set(opcount).issubset({"rz", "rx", "rxx"})) + self.assertTrue(np.allclose(Operator(qc_transpiled), Operator(qc))) + if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index 89c6ad734c03..764253f46d4b 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ commands = [testenv:lint] basepython = python3 commands = + ruff check qiskit test tools examples setup.py black --check {posargs} qiskit test tools examples setup.py pylint -rn qiskit test tools # This line is commented out until #6649 merges. We can't run this currently @@ -38,6 +39,7 @@ commands = basepython = python3 allowlist_externals = git commands = + ruff check qiskit test tools examples setup.py black --check {posargs} qiskit test tools examples setup.py -git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest python {toxinidir}/tools/pylint_incr.py -rn -j4 -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py