diff --git a/Cargo.lock b/Cargo.lock index 0a7b932a7019..b18bff37a8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -120,22 +120,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -291,10 +291,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -323,7 +323,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -334,7 +334,7 @@ checksum = "5322a90066ddae2b705096eb9e10c465c0498ae93bf9bdd6437415327c88e3bb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -580,12 +580,6 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -667,9 +661,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -677,16 +671,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.22" @@ -894,9 +878,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec170733ca37175f5d75a5bea5911d6ff45d2cd52849ce98b685394e4f2f37f4" +checksum = "cf314fca279e6e6ac2126a4ff98f26d88aa4ad06bc68fb6ae5cf4bd706758311" dependencies = [ "libc", "ndarray", @@ -979,29 +963,6 @@ dependencies = [ "xshell", ] -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - [[package]] name = "paste" version = "1.0.15" @@ -1010,9 +971,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -1021,9 +982,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -1031,22 +992,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -1065,9 +1026,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" @@ -1080,9 +1041,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", @@ -1115,9 +1076,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -1143,7 +1104,7 @@ checksum = "d315b3197b780e4873bc0e11251cb56a33f65a6032a3d39b8d1405c255513766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1161,9 +1122,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -1173,7 +1134,7 @@ dependencies = [ "memoffset", "num-bigint", "num-complex", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -1184,9 +1145,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" dependencies = [ "once_cell", "target-lexicon", @@ -1194,9 +1155,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" dependencies = [ "libc", "pyo3-build-config", @@ -1204,27 +1165,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1439,20 +1400,11 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" -[[package]] -name = "redox_syscall" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1462,9 +1414,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1473,9 +1425,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rowan" @@ -1531,12 +1483,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "seq-macro" version = "0.3.5" @@ -1560,7 +1506,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1602,9 +1548,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1654,7 +1600,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1671,9 +1617,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" @@ -1683,21 +1629,21 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unindent" @@ -1960,5 +1906,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index 5d7cfa67e66a..512ee095732e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,13 @@ license = "Apache-2.0" # # Each crate can add on specific features freely as it inherits. [workspace.dependencies] -bytemuck = "1.18" +bytemuck = "1.19" indexmap.version = "2.6.0" hashbrown.version = "0.14.5" num-bigint = "0.4" num-complex = "0.4" -ndarray = "^0.15.6" -numpy = "0.21.0" +ndarray = "0.15" +numpy = "0.22.0" smallvec = "1.13" thiserror = "1.0" rustworkx-core = "0.15" @@ -34,7 +34,7 @@ rayon = "1.10" # 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-py39"] } +pyo3 = { version = "0.22.5", features = ["abi3-py39"] } # These are our own crates. qiskit-accelerate = { path = "crates/accelerate" } diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d95f680a1666..3bf09fd551ea 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -60,4 +60,4 @@ version = "0.18.22" features = ["macro"] [features] -cache_pygates = ["qiskit-circuit/cache_pygates"] \ No newline at end of file +cache_pygates = ["qiskit-circuit/cache_pygates"] diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs index f7143d910b98..b42be53f231c 100644 --- a/crates/accelerate/src/barrier_before_final_measurement.rs +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -24,6 +24,7 @@ use qiskit_circuit::Qubit; static FINAL_OP_NAMES: [&str; 2] = ["measure", "barrier"]; #[pyfunction] +#[pyo3(signature=(dag, label=None))] pub fn barrier_before_final_measurements( py: Python, dag: &mut DAGCircuit, diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index da0592093817..4d4702d18b49 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -30,10 +30,7 @@ fn recurse<'py>( let check_qubits = |qubits: &[Qubit]| -> bool { match wire_map { Some(wire_map) => { - let mapped_bits = [ - wire_map[qubits[0].0 as usize], - wire_map[qubits[1].0 as usize], - ]; + let mapped_bits = [wire_map[qubits[0].index()], wire_map[qubits[1].index()]]; edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()]) } None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), @@ -58,7 +55,7 @@ fn recurse<'py>( .map(|inner| { let outer = qubits[inner]; match wire_map { - Some(wire_map) => wire_map[outer.0 as usize], + Some(wire_map) => wire_map[outer.index()], None => outer, } }) diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index 3b664d0b5fcd..e17357e7cec2 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -102,6 +102,7 @@ fn random_unitaries(seed: u64, size: usize) -> impl Iterator SmallVec<[Option; 2]> { let mut qubits_g2: HashMap<&Qubit, Qubit> = HashMap::with_capacity(second_qargs.len()); second_qargs.iter().enumerate().for_each(|(i_g1, q_g1)| { - qubits_g2.insert_unique_unchecked(q_g1, Qubit(i_g1 as u32)); + qubits_g2.insert_unique_unchecked(q_g1, Qubit::new(i_g1)); }); first_qargs @@ -613,6 +613,7 @@ impl CommutationLibrary { #[pymethods] impl CommutationLibrary { #[new] + #[pyo3(signature=(py_any=None))] fn new(py_any: Option>) -> Self { match py_any { Some(pyob) => CommutationLibrary { diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 7d6326d5c496..81dc0ba24027 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -43,8 +43,8 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { let qargs = dag.get_qargs(inst.qubits); - let index0 = qargs[0].0 as usize; - let index1 = qargs[1].0 as usize; + let index0 = qargs[0].index(); + let index1 = qargs[1].index(); mapping.swap(index0, index1); } ("permutation", None) => { @@ -55,7 +55,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult = dag .get_qargs(inst.qubits) .iter() - .map(|q| q.0 as usize) + .map(|q| q.index()) .collect(); let remapped_qindices: Vec = (0..qindices.len()) @@ -79,9 +79,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult = qargs .iter() - .map(|q| q.0 as usize) - .map(|q| mapping[q]) - .map(|q| Qubit(q.try_into().unwrap())) + .map(|q| Qubit::new(mapping[q.index()])) .collect(); new_dag.apply_operation_back( @@ -92,7 +90,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult FromPyObject<'py> for GateOper { - fn extract(ob: &'py PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let op_struct: OperationFromPython = ob.extract()?; Ok(Self { operation: op_struct.operation, diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index b61733ae1512..1fe3cb254914 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -42,7 +42,7 @@ pub struct ErrorMap { #[pymethods] impl ErrorMap { #[new] - #[pyo3(text_signature = "(/, size=None)")] + #[pyo3(signature=(size=None))] fn new(size: Option) -> Self { match size { Some(size) => ErrorMap { @@ -100,6 +100,7 @@ impl ErrorMap { Ok(self.error_map.contains_key(&key)) } + #[pyo3(signature=(key, default=None))] fn get(&self, py: Python, key: [PhysicalQubit; 2], default: Option) -> PyObject { match self.error_map.get(&key).copied() { Some(val) => val.to_object(py), diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 98333cad39d2..4bcf5773e354 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -52,6 +52,7 @@ pub struct OneQubitGateErrorMap { #[pymethods] impl OneQubitGateErrorMap { #[new] + #[pyo3(signature=(num_qubits=None))] fn new(num_qubits: Option) -> Self { OneQubitGateErrorMap { error_map: match num_qubits { @@ -392,6 +393,7 @@ fn circuit_rr( } #[pyfunction] +#[pyo3(signature=(target_basis, theta, phi, lam, phase, simplify, atol=None))] pub fn generate_circuit( target_basis: &EulerBasis, theta: f64, @@ -673,7 +675,7 @@ impl Default for EulerBasisSet { } #[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)] -#[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer")] +#[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer", eq, eq_int)] pub enum EulerBasis { U3 = 0, U321 = 1, @@ -808,6 +810,7 @@ fn compute_error_str( } #[pyfunction] +#[pyo3(signature=(circuit, qubit, error_map=None))] pub fn compute_error_list( circuit: Vec>, qubit: usize, @@ -1103,7 +1106,7 @@ pub(crate) fn optimize_1q_gates_decomposition( continue; } } - if basis_gates_per_qubit[qubit.0 as usize].is_none() { + if basis_gates_per_qubit[qubit.index()].is_none() { let basis_gates = match target { Some(target) => Some( target @@ -1115,11 +1118,11 @@ pub(crate) fn optimize_1q_gates_decomposition( basis.map(|basis| basis.iter().map(|x| x.as_str()).collect()) } }; - basis_gates_per_qubit[qubit.0 as usize] = basis_gates; + basis_gates_per_qubit[qubit.index()] = basis_gates; } - let basis_gates = &basis_gates_per_qubit[qubit.0 as usize].as_ref(); + let basis_gates = &basis_gates_per_qubit[qubit.index()].as_ref(); - let target_basis_set = &mut target_basis_per_qubit[qubit.0 as usize]; + let target_basis_set = &mut target_basis_per_qubit[qubit.index()]; if !target_basis_set.initialized() { match target { Some(_target) => EULER_BASES @@ -1168,7 +1171,7 @@ pub(crate) fn optimize_1q_gates_decomposition( target_basis_set.remove(EulerBasis::ZSX); } } - let target_basis_set = &target_basis_per_qubit[qubit.0 as usize]; + let target_basis_set = &target_basis_per_qubit[qubit.index()]; let operator = raw_run .iter() .map(|node_index| { @@ -1201,7 +1204,7 @@ pub(crate) fn optimize_1q_gates_decomposition( let sequence = unitary_to_gate_sequence_inner( aview2(&operator), target_basis_set, - qubit.0 as usize, + qubit.index(), None, true, None, diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 7c7d9d898726..3143a11a22a2 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -106,7 +106,7 @@ where let block_ok = if let Some(mapping) = qubit_mapping { let mapping = inst_qargs // Create a temp mapping for the recursive call .iter() - .map(|q| mapping[q.0 as usize]) + .map(|q| mapping[q.index()]) .collect::>(); check_gate_direction(py, &inner_dag, gate_complies, Some(&mapping))? @@ -128,8 +128,8 @@ where Some(mapping) => gate_complies( packed_inst, &[ - mapping[inst_qargs[0].0 as usize], - mapping[inst_qargs[1].0 as usize], + mapping[inst_qargs[0].index()], + mapping[inst_qargs[1].index()], ], ), None => gate_complies(packed_inst, inst_qargs), diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 69f656c724c9..81dc5cd0284a 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -42,7 +42,7 @@ fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult PyResult = - HashMap::from_iter((0..dag.num_qubits()).map(|i| { - ( - Qubit(i.try_into().unwrap()), - PhysicalQubit::new(i.try_into().unwrap()), - ) - })); + let wire_map: HashMap = HashMap::from_iter( + (0..dag.num_qubits()).map(|i| (Qubit::new(i), PhysicalQubit::new(i.try_into().unwrap()))), + ); // Process the DAG. for gate in dag.op_nodes(true) { diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index 93b1036b608e..dcd6e71fafa8 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -49,7 +49,7 @@ macro_rules! qubit_newtype { } impl pyo3::FromPyObject<'_> for $id { - fn extract(ob: &PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { Ok(Self(ob.extract()?)) } } @@ -60,6 +60,10 @@ macro_rules! qubit_newtype { fn get_dtype_bound(py: Python<'_>) -> Bound<'_, numpy::PyArrayDescr> { u32::get_dtype_bound(py) } + + fn clone_ref(&self, _py: Python<'_>) -> Self { + *self + } } }; } diff --git a/crates/accelerate/src/results/marginalization.rs b/crates/accelerate/src/results/marginalization.rs index b9e7ec4ba65c..83fb5097ac36 100644 --- a/crates/accelerate/src/results/marginalization.rs +++ b/crates/accelerate/src/results/marginalization.rs @@ -62,6 +62,7 @@ fn marginalize( } #[pyfunction] +#[pyo3(signature=(counts, indices=None))] pub fn marginal_counts( counts: HashMap, indices: Option>, @@ -70,6 +71,7 @@ pub fn marginal_counts( } #[pyfunction] +#[pyo3(signature=(counts, indices=None))] pub fn marginal_distribution( counts: HashMap, indices: Option>, diff --git a/crates/accelerate/src/sabre/heuristic.rs b/crates/accelerate/src/sabre/heuristic.rs index 32da6e414025..ea3b73265c77 100644 --- a/crates/accelerate/src/sabre/heuristic.rs +++ b/crates/accelerate/src/sabre/heuristic.rs @@ -17,7 +17,7 @@ use pyo3::Python; /// Affect the dynamic scaling of the weight of node-set-based heuristics (basic and lookahead). #[pyclass] -#[pyo3(module = "qiskit._accelerate.sabre", frozen)] +#[pyo3(module = "qiskit._accelerate.sabre", frozen, eq)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum SetScaling { /// No dynamic scaling of the weight. diff --git a/crates/accelerate/src/sabre/neighbor_table.rs b/crates/accelerate/src/sabre/neighbor_table.rs index f50eeb1b928b..8ab80dd81a2a 100644 --- a/crates/accelerate/src/sabre/neighbor_table.rs +++ b/crates/accelerate/src/sabre/neighbor_table.rs @@ -67,7 +67,7 @@ impl std::ops::Index for NeighborTable { #[pymethods] impl NeighborTable { #[new] - #[pyo3(text_signature = "(/, adjacency_matrix=None)")] + #[pyo3(signature = (adjacency_matrix=None))] pub fn new(adjacency_matrix: Option>) -> PyResult { let run_in_parallel = getenv_use_multiple_threads(); let neighbors = match adjacency_matrix { diff --git a/crates/accelerate/src/sabre/route.rs b/crates/accelerate/src/sabre/route.rs index 0fd594993943..acc3e50f7358 100644 --- a/crates/accelerate/src/sabre/route.rs +++ b/crates/accelerate/src/sabre/route.rs @@ -442,6 +442,7 @@ impl<'a, 'b> RoutingState<'a, 'b> { /// logical position of the qubit that began in position `i`. #[pyfunction] #[allow(clippy::too_many_arguments)] +#[pyo3(signature=(dag, neighbor_table, distance_matrix, heuristic, initial_layout, num_trials, seed=None, run_in_parallel=None))] pub fn sabre_routing( py: Python, dag: &SabreDAG, diff --git a/crates/accelerate/src/stochastic_swap.rs b/crates/accelerate/src/stochastic_swap.rs index 5260c85b3d42..a102d4525771 100644 --- a/crates/accelerate/src/stochastic_swap.rs +++ b/crates/accelerate/src/stochastic_swap.rs @@ -236,7 +236,7 @@ fn swap_trial( /// will be ``(None, None, max int)``. #[pyfunction] #[pyo3( - text_signature = "(num_trials, num_qubits, int_layout, int_qubit_subset, int_gates, cdist, cdist2, edges, /, seed=None)" + signature = (num_trials, num_qubits, int_layout, int_qubit_subset, int_gates, cdist, cdist2, edges, seed=None) )] pub fn swap_trials( num_trials: u64, diff --git a/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs b/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs index d54afe64f68c..cf0a0200fc00 100644 --- a/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs +++ b/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs @@ -173,52 +173,52 @@ fn reduce_cost( gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); } else if n0 == 2 { gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); } if n1 == 1 { gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); } else if n1 == 2 { gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); } gates.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit0 as u32), Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit0), Qubit::new(qubit1)], )); return Ok((reduced_cliff, new_cost)); @@ -242,19 +242,19 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::ZGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else if !destab_phase && stab_phase { gates.push(( StandardGate::XGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else if destab_phase && stab_phase { gates.push(( StandardGate::YGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } @@ -268,7 +268,7 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } } else if !stab_z && stab_x { @@ -276,31 +276,31 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else { if !destab_z { gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } } diff --git a/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs index 81ddfb1d6503..eca0b78951fd 100644 --- a/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs +++ b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs @@ -214,7 +214,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); } @@ -222,7 +222,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_h(*qubit); } @@ -230,12 +230,12 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); self.symplectic_matrix.prepend_h(*qubit); @@ -244,12 +244,12 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_h(*qubit); self.symplectic_matrix.prepend_s(*qubit); @@ -258,17 +258,17 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); self.symplectic_matrix.prepend_h(*qubit); @@ -304,7 +304,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SwapGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit_a as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit_a)], )); self.symplectic_matrix.prepend_swap(min_qubit, qubit_a); @@ -327,7 +327,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit)], )); self.symplectic_matrix.prepend_cx(min_qubit, qubit); } @@ -336,7 +336,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(qubit), Qubit::new(min_qubit)], )); self.symplectic_matrix.prepend_cx(qubit, min_qubit); } @@ -347,7 +347,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit_b as u32), Qubit(*qubit as u32)], + smallvec![Qubit::new(qubit_b), Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_cx(qubit_b, *qubit); } @@ -358,21 +358,21 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit_b as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit_b)], )); self.symplectic_matrix.prepend_cx(min_qubit, qubit_b); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit_b as u32)], + smallvec![Qubit::new(qubit_b)], )); self.symplectic_matrix.prepend_h(qubit_b); gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit_b as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(qubit_b), Qubit::new(min_qubit)], )); self.symplectic_matrix.prepend_cx(qubit_b, min_qubit); } @@ -387,8 +387,8 @@ impl GreedyCliffordSynthesis<'_> { StandardGate::CXGate, smallvec![], smallvec![ - Qubit(a_qubits[2 * qubit + 1] as u32), - Qubit(a_qubits[2 * qubit] as u32) + Qubit::new(a_qubits[2 * qubit + 1]), + Qubit::new(a_qubits[2 * qubit]) ], )); self.symplectic_matrix @@ -397,7 +397,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(a_qubits[2 * qubit] as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(a_qubits[2 * qubit]), Qubit::new(min_qubit)], )); self.symplectic_matrix .prepend_cx(a_qubits[2 * qubit], min_qubit); @@ -405,10 +405,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![ - Qubit(min_qubit as u32), - Qubit(a_qubits[2 * qubit + 1] as u32) - ], + smallvec![Qubit::new(min_qubit), Qubit::new(a_qubits[2 * qubit + 1])], )); self.symplectic_matrix .prepend_cx(min_qubit, a_qubits[2 * qubit + 1]); diff --git a/crates/accelerate/src/synthesis/clifford/utils.rs b/crates/accelerate/src/synthesis/clifford/utils.rs index 4415a8e1aff1..fa2e33561a46 100644 --- a/crates/accelerate/src/synthesis/clifford/utils.rs +++ b/crates/accelerate/src/synthesis/clifford/utils.rs @@ -224,19 +224,19 @@ impl Clifford { .iter() .try_for_each(|(gate, _params, qubits)| match *gate { StandardGate::SGate => { - clifford.append_s(qubits[0].0 as usize); + clifford.append_s(qubits[0].index()); Ok(()) } StandardGate::HGate => { - clifford.append_h(qubits[0].0 as usize); + clifford.append_h(qubits[0].index()); Ok(()) } StandardGate::CXGate => { - clifford.append_cx(qubits[0].0 as usize, qubits[1].0 as usize); + clifford.append_cx(qubits[0].index(), qubits[1].index()); Ok(()) } StandardGate::SwapGate => { - clifford.append_swap(qubits[0].0 as usize, qubits[1].0 as usize); + clifford.append_swap(qubits[0].index(), qubits[1].index()); Ok(()) } _ => Err(format!("Unsupported gate {:?}", gate)), @@ -287,21 +287,21 @@ pub fn adjust_final_pauli_gates( gate_seq.push(( StandardGate::YGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } else if delta_phase_pre[qubit] { // println!("=> Adding Z-gate on {}", qubit); gate_seq.push(( StandardGate::ZGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } else if delta_phase_pre[qubit + num_qubits] { // println!("=> Adding X-gate on {}", qubit); gate_seq.push(( StandardGate::XGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } } diff --git a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs index 3b30c3f53fe2..80128b077e7d 100644 --- a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs +++ b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs @@ -26,15 +26,15 @@ use crate::QiskitError; /// For example, for 4 controls we require 3 auxiliaries and create the circuit /// /// control_0: ──■────────────── -/// │ +/// │ /// control_1: ──■────────────── -/// │ +/// │ /// control_2: ──┼────■───────── -/// │ │ +/// │ │ /// control_3: ──┼────┼────■──── -/// ┌─┴─┐ │ │ +/// ┌─┴─┐ │ │ /// aux_0: ┤ X ├──■────┼──── -/// └───┘┌─┴─┐ │ +/// └───┘┌─┴─┐ │ /// aux_1: ─────┤ X ├──■──── /// └───┘┌─┴─┐ "master control" qubit: controlling on this /// aux_2: ──────────┤ X ├── <-- implements a controlled operation on all qubits @@ -57,11 +57,7 @@ fn ccx_chain<'a>( Ok(( StandardGate::CCXGate.into(), smallvec![], - vec![ - Qubit(ctrl1 as u32), - Qubit(ctrl2 as u32), - Qubit(target as u32), - ], + vec![Qubit::new(ctrl1), Qubit::new(ctrl2), Qubit::new(target)], vec![], )) }) @@ -108,6 +104,7 @@ pub fn mcmt_v_chain( } let packed_controlled_gate = controlled_gate.operation; + let gate_params = controlled_gate.params; let num_qubits = if num_ctrl_qubits > 1 { 2 * num_ctrl_qubits - 1 + num_target_qubits } else { @@ -123,7 +120,7 @@ pub fn mcmt_v_chain( Ok(( PackedOperation::from_standard(StandardGate::XGate), smallvec![] as SmallVec<[Param; 3]>, - vec![Qubit(index as u32)], + vec![Qubit::new(index)], vec![] as Vec, )) }); @@ -139,11 +136,8 @@ pub fn mcmt_v_chain( let targets = (0..num_target_qubits).map(|i| { Ok(( packed_controlled_gate.clone(), - smallvec![] as SmallVec<[Param; 3]>, - vec![ - Qubit(master_control as u32), - Qubit((num_ctrl_qubits + i) as u32), - ], + gate_params.clone(), + vec![Qubit::new(master_control), Qubit::new(num_ctrl_qubits + i)], vec![] as Vec, )) }); diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 2f84776cb5fd..2cc0b02f2c66 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -54,7 +54,7 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes ( StandardGate::SwapGate, smallvec![], - smallvec![Qubit(*i as u32), Qubit(*j as u32)], + smallvec![Qubit::new(*i), Qubit::new(*j)], ) }), Param::Float(0.0), @@ -77,7 +77,7 @@ fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult FromPyObject<'py> for NormalOperation { - fn extract(ob: &'py PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let operation: OperationFromPython = ob.extract()?; Ok(Self { operation: operation.operation, params: operation.params, - op_object: ob.into(), + op_object: ob.clone().unbind(), }) } } @@ -371,7 +371,7 @@ impl Target { /// properties (InstructionProperties): The properties to set for this instruction /// Raises: /// KeyError: If ``instruction`` or ``qarg`` are not in the target - #[pyo3(text_signature = "(instruction, qargs, properties, /,)")] + #[pyo3(signature = (instruction, qargs=None, properties=None))] fn update_instruction_properties( &mut self, instruction: String, diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index ff084323b716..7f20caea104f 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -216,6 +216,22 @@ fn py_decompose_two_qubit_product_gate( )) } +/// Computes the Weyl coordinates for a given two-qubit unitary matrix. +/// +/// Args: +/// U (np.ndarray): Input two-qubit unitary. +/// +/// Returns: +/// np.ndarray: Array of the 3 Weyl coordinates. +#[pyfunction] +fn weyl_coordinates(py: Python, unitary: PyReadonlyArray2) -> PyObject { + let array = unitary.as_array(); + __weyl_coordinates(array.into_faer_complex()) + .to_vec() + .into_pyarray_bound(py) + .into() +} + fn __weyl_coordinates(unitary: MatRef) -> [f64; 3] { let uscaled = scale(C1 / unitary.determinant().powf(0.25)) * unitary; let uup = transform_from_magic_basis(uscaled); @@ -407,8 +423,8 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2< const DEFAULT_FIDELITY: f64 = 1.0 - 1.0e-9; -#[derive(Clone, Debug, Copy)] -#[pyclass(module = "qiskit._accelerate.two_qubit_decompose")] +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[pyclass(module = "qiskit._accelerate.two_qubit_decompose", eq)] pub enum Specialization { General, IdEquiv, @@ -1030,6 +1046,7 @@ static IPX: GateArray1Q = [[C_ZERO, IM], [IM, C_ZERO]]; #[pymethods] impl TwoQubitWeylDecomposition { #[staticmethod] + #[pyo3(signature=(angles, matrices, specialization, default_euler_basis, calculated_fidelity, requested_fidelity=None))] fn _from_state( angles: [f64; 4], matrices: [PyReadonlyArray2; 5], @@ -2352,6 +2369,7 @@ pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(local_equivalence))?; m.add_wrapped(wrap_pyfunction!(py_trace_to_fid))?; m.add_wrapped(wrap_pyfunction!(py_ud))?; + m.add_wrapped(wrap_pyfunction!(weyl_coordinates))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/accelerate/src/unitary_compose.rs b/crates/accelerate/src/unitary_compose.rs index b40f6c3eb564..7c4da99dd7d2 100644 --- a/crates/accelerate/src/unitary_compose.rs +++ b/crates/accelerate/src/unitary_compose.rs @@ -57,7 +57,7 @@ pub fn compose( let mat = per_qubit_shaped(overall_unitary); let indices = qubits .iter() - .map(|q| num_indices - 1 - q.0 as usize) + .map(|q| num_indices - 1 - q.index()) .collect::>(); let num_rows = usize::pow(2, num_indices as u32); @@ -219,7 +219,7 @@ fn _ind(i: usize, reversed: bool) -> usize { /// For equally sized matrices, ``left`` and ``right``, check whether all entries are close /// by the criterion -/// +/// /// |left_ij - right_ij| <= atol + rtol * right_ij /// /// This is analogous to NumPy's ``allclose`` function. diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml index ef79525ab761..8bb59e758786 100644 --- a/crates/circuit/Cargo.toml +++ b/crates/circuit/Cargo.toml @@ -23,7 +23,7 @@ itertools.workspace = true [dependencies.pyo3] workspace = true -features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"] +features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec", "py-clone"] [dependencies.hashbrown] workspace = true @@ -38,4 +38,4 @@ workspace = true features = ["union"] [features] -cache_pygates = [] \ No newline at end of file +cache_pygates = [] diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 6a8e4af6b920..56a2560385f2 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -61,6 +61,7 @@ impl PartialEq for BitAsKey { .bind(py) .repr() .unwrap() + .as_any() .eq(other.bit.bind(py).repr().unwrap()) .unwrap() }) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 8b571e245e4b..d20163170b1a 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -898,6 +898,7 @@ impl CircuitData { Ok(()) } + #[pyo3(signature = (index=None))] pub fn pop(&mut self, py: Python<'_>, index: Option) -> PyResult { let index = index.unwrap_or(PySequenceIndex::Int(-1)); let native_index = index.with_len(self.data.len())?; diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 62d8860dbf1d..255343ac186c 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -424,6 +424,7 @@ impl CircuitInstruction { /// /// Returns: /// CircuitInstruction: A new instance with the given fields replaced. + #[pyo3(signature=(operation=None, qubits=None, clbits=None, params=None))] pub fn replace( &self, py: Python, diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e54f4da3a0c5..f08dd4f6345f 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -505,7 +505,7 @@ impl DAGCircuit { .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( self.qubits.get(qubit).unwrap().clone_ref(py), @@ -516,7 +516,7 @@ impl DAGCircuit { .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( self.clbits.get(clbit).unwrap().clone_ref(py), @@ -539,7 +539,7 @@ impl DAGCircuit { .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( self.qubits.get(qubit).unwrap().clone_ref(py), @@ -550,7 +550,7 @@ impl DAGCircuit { .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( self.clbits.get(clbit).unwrap().clone_ref(py), @@ -890,6 +890,7 @@ impl DAGCircuit { /// /// Raises: /// Exception: if the gate is of type string and params is None. + #[pyo3(signature=(gate, qubits, schedule, params=None))] fn add_calibration<'py>( &mut self, py: Python<'py>, @@ -1234,13 +1235,13 @@ def _format(operand): .drain(..) .enumerate() .filter_map(|(k, v)| { - let clbit = Clbit(k as u32); + let clbit = Clbit::new(k); if clbits.contains(&clbit) { None } else { Some(( self.clbits - .find(old_clbits.get(Clbit(k as u32)).unwrap().bind(py)) + .find(old_clbits.get(Clbit::new(k)).unwrap().bind(py)) .unwrap(), v, )) @@ -1250,7 +1251,7 @@ def _format(operand): self.clbit_io_map = (0..io_mapping.len()) .map(|idx| { - let clbit = Clbit(idx as u32); + let clbit = Clbit::new(idx); io_mapping[&clbit] }) .collect(); @@ -1442,7 +1443,7 @@ def _format(operand): .drain(..) .enumerate() .filter_map(|(k, v)| { - let qubit = Qubit(k as u32); + let qubit = Qubit::new(k); if qubits.contains(&qubit) { None } else { @@ -1458,7 +1459,7 @@ def _format(operand): self.qubit_io_map = (0..io_mapping.len()) .map(|idx| { - let qubit = Qubit(idx as u32); + let qubit = Qubit::new(idx); io_mapping[&qubit] }) .collect(); @@ -2024,7 +2025,7 @@ def _format(operand): let wire_in_dag = dag.qubits.find(&m_wire); if wire_in_dag.is_none() - || (dag.qubit_io_map.len() - 1 < wire_in_dag.unwrap().0 as usize) + || (dag.qubit_io_map.len() - 1 < wire_in_dag.unwrap().index()) { return Err(DAGCircuitError::new_err(format!( "wire {} not in self", @@ -2038,7 +2039,7 @@ def _format(operand): let m_wire = edge_map.get_item(bit)?.unwrap_or_else(|| bit.clone()); let wire_in_dag = dag.clbits.find(&m_wire); if wire_in_dag.is_none() - || dag.clbit_io_map.len() - 1 < wire_in_dag.unwrap().0 as usize + || dag.clbit_io_map.len() - 1 < wire_in_dag.unwrap().index() { return Err(DAGCircuitError::new_err(format!( "wire {} not in self", @@ -2145,11 +2146,12 @@ def _format(operand): /// /// Raises: /// DAGCircuitError: If the DAG is invalid + #[pyo3(signature=(ignore=None))] fn idle_wires(&self, py: Python, ignore: Option<&Bound>) -> PyResult> { let mut result: Vec = Vec::new(); let wires = (0..self.qubit_io_map.len()) - .map(|idx| Wire::Qubit(Qubit(idx as u32))) - .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit(idx as u32)))) + .map(|idx| Wire::Qubit(Qubit::new(idx))) + .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) .chain(self.var_input_map.keys(py).map(Wire::Var)); match ignore { Some(ignore) => { @@ -2651,7 +2653,7 @@ def _format(operand): /// /// Returns: /// generator(DAGOpNode, DAGInNode, or DAGOutNode): node in topological order - #[pyo3(name = "topological_nodes")] + #[pyo3(name = "topological_nodes", signature=(key=None))] fn py_topological_nodes( &self, py: Python, @@ -2687,7 +2689,7 @@ def _format(operand): /// /// Returns: /// generator(DAGOpNode): op node in topological order - #[pyo3(name = "topological_op_nodes")] + #[pyo3(name = "topological_op_nodes", signature=(key=None))] fn py_topological_op_nodes( &self, py: Python, @@ -3593,20 +3595,20 @@ def _format(operand): match self.dag.node_weight(*node) { Some(w) => match w { NodeType::ClbitIn(b) => { - let clbit_in = new_dag.clbit_io_map[b.0 as usize][0]; + let clbit_in = new_dag.clbit_io_map[b.index()][0]; node_map.insert(*node, clbit_in); } NodeType::ClbitOut(b) => { - let clbit_out = new_dag.clbit_io_map[b.0 as usize][1]; + let clbit_out = new_dag.clbit_io_map[b.index()][1]; node_map.insert(*node, clbit_out); } NodeType::QubitIn(q) => { - let qbit_in = new_dag.qubit_io_map[q.0 as usize][0]; + let qbit_in = new_dag.qubit_io_map[q.index()][0]; node_map.insert(*node, qbit_in); non_classical = true; } NodeType::QubitOut(q) => { - let qbit_out = new_dag.qubit_io_map[q.0 as usize][1]; + let qbit_out = new_dag.qubit_io_map[q.index()][1]; node_map.insert(*node, qbit_out); non_classical = true; } @@ -3649,7 +3651,7 @@ def _format(operand): .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { if new_dag.dag.edges(*in_node).next().is_none() { new_dag @@ -3661,7 +3663,7 @@ def _format(operand): .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { if new_dag.dag.edges(*in_node).next().is_none() { new_dag @@ -3814,7 +3816,8 @@ def _format(operand): /// Yield: /// edge: the edge as a tuple with the format /// (source node, destination node, edge wire) - fn edges(&self, nodes: Option>, py: Python) -> PyResult> { + #[pyo3(signature=(nodes=None))] + fn edges(&self, py: Python, nodes: Option>) -> PyResult> { let get_node_index = |obj: &Bound| -> PyResult { Ok(obj.downcast::()?.borrow().node.unwrap()) }; @@ -4606,7 +4609,7 @@ def _format(operand): })?; let output_node_index = self .qubit_io_map - .get(output_qubit.0 as usize) + .get(output_qubit.index()) .map(|x| x[1]) .ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -4718,6 +4721,7 @@ def _format(operand): module.call_method1("dag_drawer", (slf, scale, filename, style)) } + #[pyo3(signature=(graph_attrs=None, node_attrs=None, edge_attrs=None))] fn _to_dot<'py>( &self, py: Python<'py>, @@ -5064,7 +5068,7 @@ impl DAGCircuit { let color_fn = move |edge_index: EdgeIndex| -> Result, Infallible> { let wire = self.dag.edge_weight(edge_index).unwrap(); match wire { - Wire::Qubit(index) => Ok(Some(index.0 as usize)), + Wire::Qubit(index) => Ok(Some(index.index())), _ => Ok(None), } }; @@ -5152,11 +5156,11 @@ impl DAGCircuit { .qargs_interner .get(qubits_id) .iter() - .map(|q| self.qubit_io_map.get(q.0 as usize).map(|x| x[1]).unwrap()) + .map(|q| self.qubit_io_map.get(q.index()).map(|x| x[1]).unwrap()) .chain( all_cbits .iter() - .map(|c| self.clbit_io_map.get(c.0 as usize).map(|x| x[1]).unwrap()), + .map(|c| self.clbit_io_map.get(c.index()).map(|x| x[1]).unwrap()), ) .chain( vars.iter() @@ -5218,8 +5222,8 @@ impl DAGCircuit { .qargs_interner .get(qubits_id) .iter() - .map(|q| self.qubit_io_map[q.0 as usize][0]) - .chain(all_cbits.iter().map(|c| self.clbit_io_map[c.0 as usize][0])) + .map(|q| self.qubit_io_map[q.index()][0]) + .chain(all_cbits.iter().map(|c| self.clbit_io_map[c.index()][0])) .collect(); if let Some(vars) = vars { for var in vars { @@ -5306,7 +5310,7 @@ impl DAGCircuit { ) -> PyResult { // Check that all qargs are within an acceptable range qargs.iter().try_for_each(|qarg| { - if qarg.0 as usize >= self.num_qubits() { + if qarg.index() >= self.num_qubits() { return Err(PyValueError::new_err(format!( "Qubit index {} is out of range. This DAGCircuit currently has only {} qubits.", qarg.0, @@ -5318,7 +5322,7 @@ impl DAGCircuit { // Check that all cargs are within an acceptable range cargs.iter().try_for_each(|carg| { - if carg.0 as usize >= self.num_clbits() { + if carg.index() >= self.num_clbits() { return Err(PyValueError::new_err(format!( "Clbit index {} is out of range. This DAGCircuit currently has only {} clbits.", carg.0, @@ -5422,12 +5426,12 @@ impl DAGCircuit { fn is_wire_idle(&self, py: Python, wire: &Wire) -> PyResult { let (input_node, output_node) = match wire { Wire::Qubit(qubit) => ( - self.qubit_io_map[qubit.0 as usize][0], - self.qubit_io_map[qubit.0 as usize][1], + self.qubit_io_map[qubit.index()][0], + self.qubit_io_map[qubit.index()][1], ), Wire::Clbit(clbit) => ( - self.clbit_io_map[clbit.0 as usize][0], - self.clbit_io_map[clbit.0 as usize][1], + self.clbit_io_map[clbit.index()][0], + self.clbit_io_map[clbit.index()][1], ), Wire::Var(var) => ( self.var_input_map.get(py, var).unwrap(), @@ -5571,7 +5575,7 @@ impl DAGCircuit { fn add_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { let (in_node, out_node) = match wire { Wire::Qubit(qubit) => { - if (qubit.0 as usize) >= self.qubit_io_map.len() { + if (qubit.index()) >= self.qubit_io_map.len() { let input_node = self.dag.add_node(NodeType::QubitIn(qubit)); let output_node = self.dag.add_node(NodeType::QubitOut(qubit)); self.qubit_io_map.push([input_node, output_node]); @@ -5581,7 +5585,7 @@ impl DAGCircuit { } } Wire::Clbit(clbit) => { - if (clbit.0 as usize) >= self.clbit_io_map.len() { + if (clbit.index()) >= self.clbit_io_map.len() { let input_node = self.dag.add_node(NodeType::ClbitIn(clbit)); let output_node = self.dag.add_node(NodeType::ClbitOut(clbit)); self.clbit_io_map.push([input_node, output_node]); @@ -5614,8 +5618,8 @@ impl DAGCircuit { pub fn nodes_on_wire(&self, py: Python, wire: &Wire, only_ops: bool) -> Vec { let mut nodes = Vec::new(); let mut current_node = match wire { - Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.0 as usize).map(|x| x[0]), - Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.0 as usize).map(|x| x[0]), + Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.index()).map(|x| x[0]), + Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.index()).map(|x| x[0]), Wire::Var(var) => self.var_input_map.get(py, var), }; @@ -5643,8 +5647,8 @@ impl DAGCircuit { fn remove_idle_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { let [in_node, out_node] = match wire { - Wire::Qubit(qubit) => self.qubit_io_map[qubit.0 as usize], - Wire::Clbit(clbit) => self.clbit_io_map[clbit.0 as usize], + Wire::Qubit(qubit) => self.qubit_io_map[qubit.index()], + Wire::Clbit(clbit) => self.clbit_io_map[clbit.index()], Wire::Var(var) => [ self.var_input_map.remove(py, &var).unwrap(), self.var_output_map.remove(py, &var).unwrap(), @@ -5973,7 +5977,7 @@ impl DAGCircuit { // Add wire from pred to succ if no ops on mapped wire on ``other`` for (in_dag_wire, self_wire) in qubit_map.iter() { - let [input_node, out_node] = other.qubit_io_map[in_dag_wire.0 as usize]; + let [input_node, out_node] = other.qubit_io_map[in_dag_wire.index()]; if other.dag.find_edge(input_node, out_node).is_some() { let pred = self .dag @@ -6002,7 +6006,7 @@ impl DAGCircuit { } } for (in_dag_wire, self_wire) in clbit_map.iter() { - let [input_node, out_node] = other.clbit_io_map[in_dag_wire.0 as usize]; + let [input_node, out_node] = other.clbit_io_map[in_dag_wire.index()]; if other.dag.find_edge(input_node, out_node).is_some() { let pred = self .dag @@ -6118,11 +6122,11 @@ impl DAGCircuit { let wire_input_id = match weight { Wire::Qubit(qubit) => other .qubit_io_map - .get(reverse_qubit_map[&qubit].0 as usize) + .get(reverse_qubit_map[&qubit].index()) .map(|x| x[0]), Wire::Clbit(clbit) => other .clbit_io_map - .get(reverse_clbit_map[&clbit].0 as usize) + .get(reverse_clbit_map[&clbit].index()) .map(|x| x[0]), Wire::Var(ref var) => { let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); @@ -6154,11 +6158,11 @@ impl DAGCircuit { let wire_output_id = match weight { Wire::Qubit(qubit) => other .qubit_io_map - .get(reverse_qubit_map[&qubit].0 as usize) + .get(reverse_qubit_map[&qubit].index()) .map(|x| x[1]), Wire::Clbit(clbit) => other .clbit_io_map - .get(reverse_clbit_map[&clbit].0 as usize) + .get(reverse_clbit_map[&clbit].index()) .map(|x| x[1]), Wire::Var(ref var) => { let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); @@ -6238,7 +6242,7 @@ impl DAGCircuit { } for b in self.qargs_interner.get(inst.qubits) { - if self.qubit_io_map.len() - 1 < b.0 as usize { + if self.qubit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "qubit {} not found in output map", self.qubits.get(*b).unwrap() @@ -6247,7 +6251,7 @@ impl DAGCircuit { } for b in self.cargs_interner.get(inst.clbits) { - if !self.clbit_io_map.len() - 1 < b.0 as usize { + if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "clbit {} not found in output map", self.clbits.get(*b).unwrap() @@ -6258,7 +6262,7 @@ impl DAGCircuit { if self.may_have_additional_wires(py, inst) { let (clbits, vars) = self.additional_wires(py, inst.op.view(), inst.condition())?; for b in clbits { - if !self.clbit_io_map.len() - 1 < b.0 as usize { + if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "clbit {} not found in output map", self.clbits.get(b).unwrap() @@ -6658,8 +6662,7 @@ impl DAGCircuit { ))); } let qubit_index = qc_data.qubits().find(&qubit).unwrap(); - ordered_vec[qubit_index.0 as usize] = - new_dag.add_qubit_unchecked(py, &qubit)?; + ordered_vec[qubit_index.index()] = new_dag.add_qubit_unchecked(py, &qubit)?; Ok(()) })?; Some(ordered_vec) @@ -6688,8 +6691,7 @@ impl DAGCircuit { ))); }; let clbit_index = qc_data.clbits().find(&clbit).unwrap(); - ordered_vec[clbit_index.0 as usize] = - new_dag.add_clbit_unchecked(py, &clbit)?; + ordered_vec[clbit_index.index()] = new_dag.add_clbit_unchecked(py, &clbit)?; Ok(()) })?; Some(ordered_vec) @@ -6740,7 +6742,7 @@ impl DAGCircuit { let qargs = qc_data .get_qargs(instr.qubits) .iter() - .map(|bit| qubit_mapping[bit.0 as usize]) + .map(|bit| qubit_mapping[bit.index()]) .collect(); new_dag.qargs_interner.insert_owned(qargs) } else { @@ -6753,7 +6755,7 @@ impl DAGCircuit { let qargs = qc_data .get_cargs(instr.clbits) .iter() - .map(|bit| clbit_mapping[bit.0 as usize]) + .map(|bit| clbit_mapping[bit.index()]) .collect(); new_dag.cargs_interner.insert_owned(qargs) } else { diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 4d82cf31e813..6c3a2d15fbad 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -85,6 +85,7 @@ impl DAGNode { self.node.map(|node| node.index()) } + #[pyo3(signature=(index=None))] fn __setstate__(&mut self, index: Option) { self.node = index.map(NodeIndex::new); } diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 12351dcbbc70..8705d52d1aa7 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -35,9 +35,50 @@ use pyo3::types::{PySequence, PyTuple}; pub type BitType = u32; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject)] pub struct Qubit(pub BitType); + +impl Qubit { + /// Construct a new Qubit object from a usize, if you have a u32 you can + /// create a `Qubit` object directly with `Qubit(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + pub fn new(index: usize) -> Self { + Qubit( + index.try_into().unwrap_or_else(|_| { + panic!("Index value '{}' exceeds the maximum bit width!", index) + }), + ) + } + + /// Convert a Qubit to a usize + #[inline(always)] + pub fn index(&self) -> usize { + self.0 as usize + } +} + #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct Clbit(pub BitType); +impl Clbit { + /// Construct a new Clbit object from a usize. if you have a u32 you can + /// create a `Clbit` object directly with `Clbit(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + pub fn new(index: usize) -> Self { + Clbit( + index.try_into().unwrap_or_else(|_| { + panic!("Index value '{}' exceeds the maximum bit width!", index) + }), + ) + } + + /// Convert a Clbit to a usize + #[inline(always)] + pub fn index(&self) -> usize { + self.0 as usize + } +} + pub struct TupleLikeArg<'py> { value: Bound<'py, PyTuple>, } @@ -92,3 +133,54 @@ pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_qubit_create() { + let expected = Qubit(12345); + let val = 12345_usize; + let result = Qubit::new(val); + assert_eq!(result, expected); + } + + #[test] + #[should_panic] + fn test_qubit_index_too_large() { + let val = u32::MAX as usize + 42; + Qubit::new(val); + } + + #[test] + fn test_clbit_create() { + let expected = Clbit(12345); + let val = 12345_usize; + let result = Clbit::new(val); + assert_eq!(result, expected); + } + + #[test] + #[should_panic] + fn test_clbit_index_too_large() { + let val = u32::MAX as usize + 42; + Clbit::new(val); + } + + #[test] + fn test_qubit_index() { + let qubit = Qubit(123456789); + let expected = 123456789_usize; + let result = qubit.index(); + assert_eq!(result, expected); + } + + #[test] + fn test_clbit_index() { + let clbit = Clbit(1234542); + let expected = 1234542_usize; + let result = clbit.index(); + assert_eq!(result, expected); + } +} diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 1a9059b08a13..67edb3aa0adc 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -278,7 +278,7 @@ impl<'a> Operation for OperationRef<'a> { #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[repr(u8)] -#[pyclass(module = "qiskit._accelerate.circuit")] +#[pyclass(module = "qiskit._accelerate.circuit", eq, eq_int)] pub enum StandardGate { GlobalPhaseGate = 0, HGate = 1, @@ -720,14 +720,6 @@ impl StandardGate { self.name() } - pub fn __eq__(&self, other: &Bound) -> Py { - let py = other.py(); - let Ok(other) = other.extract::() else { - return py.NotImplemented(); - }; - (*self == other).into_py(py) - } - pub fn __hash__(&self) -> isize { *self as isize } diff --git a/crates/circuit/src/slice.rs b/crates/circuit/src/slice.rs index d62e47f03ef3..24c378849a22 100644 --- a/crates/circuit/src/slice.rs +++ b/crates/circuit/src/slice.rs @@ -51,7 +51,7 @@ impl<'py> PySequenceIndex<'py> { } PySequenceIndex::Slice(slice) => { let indices = slice - .indices(len as ::std::os::raw::c_long) + .indices(len as isize) .map_err(PySequenceIndexError::from)?; if indices.step > 0 { Ok(SequenceIndex::PosRange { diff --git a/crates/qasm2/src/bytecode.rs b/crates/qasm2/src/bytecode.rs index fab973f2186f..df3897aa5b71 100644 --- a/crates/qasm2/src/bytecode.rs +++ b/crates/qasm2/src/bytecode.rs @@ -32,8 +32,8 @@ pub struct Bytecode { } /// The operations that are represented by the "bytecode" passed to Python. -#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, Eq, PartialEq)] pub enum OpCode { // There is only a `Gate` here, not a `GateInBasis`, because in Python space we don't have the // same strict typing requirements to satisfy. @@ -113,8 +113,8 @@ 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._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, PartialEq, Eq)] pub enum UnaryOpCode { Negate, Cos, @@ -129,8 +129,8 @@ 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._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, PartialEq, Eq)] pub enum BinaryOpCode { Add, Subtract, diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 22a771a67312..542c927ee175 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -778,6 +778,30 @@ Consult :ref:`the control-flow construction documentation ` for more information on how to build circuits with control flow. +Investigating commutation relations +----------------------------------- + +If two operations in a circuit commute, we can swap the order in which they are applied. +This can allow for optimizations and simplifications, for example, if it allows to merge +or cancel gates: + +.. code-block:: text + + ┌─────────┐ ┌─────────┐ ┌─────────┐ + q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■── q_0: ┤ Rz(1.7) ├ + └─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐ = └──┬───┬──┘ + q_1: ───────────┤ X ├───┤ X ├───┤ X ├ q_1: ───┤ X ├─── + └───┘ └───┘ └───┘ └───┘ + +Performing these optimizations are part of the transpiler, but the tools to investigate commutations +are available in the :class:`CommutationChecker`. + +.. autosummary:: + :toctree: ../stubs/ + + CommutationChecker + + .. _circuit-custom-gates: Creating custom instructions diff --git a/qiskit/circuit/library/quantum_volume.py b/qiskit/circuit/library/quantum_volume.py index 008820329d43..644a85d9af13 100644 --- a/qiskit/circuit/library/quantum_volume.py +++ b/qiskit/circuit/library/quantum_volume.py @@ -83,39 +83,49 @@ def __init__( depth = depth or num_qubits # how many layers of SU(4) width = num_qubits // 2 # how many SU(4)s fit in each layer rng = seed if isinstance(seed, np.random.Generator) else np.random.default_rng(seed) - if seed is None: + seed_name = seed + if seed_name is None: # Get the internal entropy used to seed the default RNG, if no seed was given. This # stays in the output name, so effectively stores a way of regenerating the circuit. # This is just best-effort only, for backwards compatibility, and isn't critical (if # someone needs full reproducibility, they should be manually controlling the seeding). - seed = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None) + seed_name = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None) super().__init__( - num_qubits, name="quantum_volume_" + str([num_qubits, depth, seed]).replace(" ", "") + num_qubits, + name="quantum_volume_" + str([num_qubits, depth, seed_name]).replace(" ", ""), ) - base = self if flatten else QuantumCircuit(num_qubits, name=self.name) - - # For each layer, generate a permutation of qubits - # Then generate and apply a Haar-random SU(4) to each pair - unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape(depth, width, 4, 4) - qubits = tuple(base.qubits) - for row in unitaries: - perm = rng.permutation(num_qubits) - if classical_permutation: - for w, unitary in enumerate(row): - gate = UnitaryGate(unitary, check_input=False, num_qubits=2) - qubit = 2 * w - base._append( - CircuitInstruction(gate, (qubits[perm[qubit]], qubits[perm[qubit + 1]])) - ) + if classical_permutation: + if seed is not None: + max_value = np.iinfo(np.int64).max + seed = rng.integers(max_value, dtype=np.int64) + qv_circ = quantum_volume(num_qubits, depth, seed) + qv_circ.name = self.name + if flatten: + self.compose(qv_circ, inplace=True) else: + self._append(CircuitInstruction(qv_circ.to_instruction(), tuple(self.qubits))) + else: + if seed is None: + seed = seed_name + + base = self if flatten else QuantumCircuit(num_qubits, name=self.name) + + # For each layer, generate a permutation of qubits + # Then generate and apply a Haar-random SU(4) to each pair + unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape( + depth, width, 4, 4 + ) + qubits = tuple(base.qubits) + for row in unitaries: + perm = rng.permutation(num_qubits) base._append(CircuitInstruction(PermutationGate(perm), qubits)) for w, unitary in enumerate(row): gate = UnitaryGate(unitary, check_input=False, num_qubits=2) qubit = 2 * w base._append(CircuitInstruction(gate, qubits[qubit : qubit + 2])) - if not flatten: - self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits))) + if not flatten: + self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits))) def quantum_volume( diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 39eda0ab923a..edec8e6ed030 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -431,3 +431,11 @@ def control( def inverse(self, annotated: bool = False): r"""Return inverted MCPhase gate (:math:`MCPhase(\lambda)^{\dagger} = MCPhase(-\lambda)`)""" return MCPhaseGate(-self.params[0], self.num_ctrl_qubits) + + def __eq__(self, other): + return ( + isinstance(other, MCPhaseGate) + and self.num_ctrl_qubits == other.num_ctrl_qubits + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index bed454897929..c15205790bd8 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -393,3 +393,10 @@ def __deepcopy__(self, memo=None): out = super().__deepcopy__(memo) out._params = _copy.deepcopy(out._params, memo) return out + + def __eq__(self, other): + return ( + isinstance(other, CUGate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 65bbb633fb30..ec8dc6a4f2dc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -170,6 +170,9 @@ def __array__(self, dtype=None, copy=None): lam = float(self.params[0]) return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=dtype) + def __eq__(self, other): + return isinstance(other, U1Gate) and self._compare_parameters(other) + class CU1Gate(ControlledGate): r"""Controlled-U1 gate. @@ -341,6 +344,13 @@ def __array__(self, dtype=None, copy=None): [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, eith, 0], [0, 0, 0, 1]], dtype=dtype ) + def __eq__(self, other): + return ( + isinstance(other, CU1Gate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) + class MCU1Gate(ControlledGate): r"""Multi-controlled-U1 gate. @@ -481,3 +491,11 @@ def inverse(self, annotated: bool = False): MCU1Gate: inverse gate. """ return MCU1Gate(-self.params[0], self.num_ctrl_qubits) + + def __eq__(self, other): + return ( + isinstance(other, MCU1Gate) + and self.num_ctrl_qubits == other.num_ctrl_qubits + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index 9e59cd4c5bbd..e39df591b53e 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -144,3 +144,6 @@ def __array__(self, dtype=None, copy=None): ], dtype=dtype or complex, ) + + def __eq__(self, other): + return isinstance(other, U2Gate) and self._compare_parameters(other) diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 4efccaaf92b9..ff4871b5c91d 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -178,6 +178,9 @@ def __array__(self, dtype=None, copy=None): dtype=dtype or complex, ) + def __eq__(self, other): + return isinstance(other, U3Gate) and self._compare_parameters(other) + class CU3Gate(ControlledGate): r"""Controlled-U3 gate (3-parameter two-qubit gate). @@ -368,6 +371,13 @@ def __array__(self, dtype=None, copy=None): dtype=dtype or complex, ) + def __eq__(self, other): + return ( + isinstance(other, CU3Gate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) + def _generate_gray_code(num_bits): """Generate the gray code for ``num_bits`` bits.""" diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 0a151a8476e3..1c132eb0a7e4 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3229,40 +3229,33 @@ def to_gate( def decompose( self, - gates_to_decompose: Type[Gate] | Sequence[Type[Gate]] | Sequence[str] | str | None = None, + gates_to_decompose: ( + str | Type[Instruction] | Sequence[str | Type[Instruction]] | None + ) = None, reps: int = 1, - ) -> "QuantumCircuit": - """Call a decomposition pass on this circuit, - to decompose one level (shallow decompose). + ) -> typing.Self: + """Call a decomposition pass on this circuit, to decompose one level (shallow decompose). Args: - gates_to_decompose (type or str or list(type, str)): Optional subset of gates - to decompose. Can be a gate type, such as ``HGate``, or a gate name, such - as 'h', or a gate label, such as 'My H Gate', or a list of any combination - of these. If a gate name is entered, it will decompose all gates with that - name, whether the gates have labels or not. Defaults to all gates in circuit. - reps (int): Optional number of times the circuit should be decomposed. + gates_to_decompose: Optional subset of gates to decompose. Can be a gate type, such as + ``HGate``, or a gate name, such as "h", or a gate label, such as "My H Gate", or a + list of any combination of these. If a gate name is entered, it will decompose all + gates with that name, whether the gates have labels or not. Defaults to all gates in + the circuit. + reps: Optional number of times the circuit should be decomposed. For instance, ``reps=2`` equals calling ``circuit.decompose().decompose()``. - can decompose specific gates specific time Returns: QuantumCircuit: a circuit one level decomposed """ # pylint: disable=cyclic-import from qiskit.transpiler.passes.basis.decompose import Decompose - from qiskit.transpiler.passes.synthesis import HighLevelSynthesis from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit dag = circuit_to_dag(self, copy_operations=True) - if gates_to_decompose is None: - # We should not rewrite the circuit using HLS when we have gates_to_decompose, - # or else HLS will rewrite all objects with available plugins (e.g., Cliffords, - # PermutationGates, and now also MCXGates) - dag = HighLevelSynthesis().run(dag) - - pass_ = Decompose(gates_to_decompose) + pass_ = Decompose(gates_to_decompose, apply_synthesis=True) for _ in range(reps): dag = pass_.run(dag) diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index 6736d67a214b..e3a2f63e6ca0 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -307,7 +307,7 @@ def __init__(self, label=None): def _define(self): qc = QuantumCircuit(1) - q.ry(np.pi / 2, 0) + qc.ry(np.pi / 2, 0) self.definition = qc The key thing to ensure is that for any custom gates in your Backend's basis set diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 38e471f0b0a6..1d9e88929b2d 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -184,7 +184,7 @@ def _multiply(self, other): return BasePauli(self._z, self._x, np.mod(self._phase + phase, 4)) def conjugate(self): - """Return the conjugate of each Pauli in the list.""" + """Return the complex conjugate of the Pauli with respect to the Z basis.""" complex_phase = np.mod(self._phase, 2) if np.all(complex_phase == 0): return self diff --git a/qiskit/synthesis/multi_controlled/mcmt_vchain.py b/qiskit/synthesis/multi_controlled/mcmt_vchain.py index ef565345dd08..a733f7b7fcca 100644 --- a/qiskit/synthesis/multi_controlled/mcmt_vchain.py +++ b/qiskit/synthesis/multi_controlled/mcmt_vchain.py @@ -43,6 +43,9 @@ def synth_mcmt_vchain( └───┘ └───┘ """ + if gate.num_qubits != 1: + raise ValueError("Only single qubit gates are supported as input.") + circ = QuantumCircuit._from_circuit_data( mcmt_v_chain(gate.control(), num_ctrl_qubits, num_target_qubits, ctrl_state) ) diff --git a/qiskit/synthesis/two_qubit/weyl.py b/qiskit/synthesis/two_qubit/weyl.py deleted file mode 100644 index b533b69a3696..000000000000 --- a/qiskit/synthesis/two_qubit/weyl.py +++ /dev/null @@ -1,97 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# pylint: disable=invalid-name - -"""Routines that compute and use the Weyl chamber coordinates. -""" - -from __future__ import annotations -import numpy as np - -# "Magic" basis used for the Weyl decomposition. The basis and its adjoint are stored individually -# unnormalized, but such that their matrix multiplication is still the identity. This is because -# they are only used in unitary transformations (so it's safe to do so), and `sqrt(0.5)` is not -# exactly representable in floating point. Doing it this way means that every element of the matrix -# is stored exactly correctly, and the multiplication is _exactly_ the identity rather than -# differing by 1ULP. -_B_nonnormalized = np.array([[1, 1j, 0, 0], [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0]]) -_B_nonnormalized_dagger = 0.5 * _B_nonnormalized.conj().T - - -def transform_to_magic_basis(U: np.ndarray, reverse: bool = False) -> np.ndarray: - """Transform the 4-by-4 matrix ``U`` into the magic basis. - - This method internally uses non-normalized versions of the basis to minimize the floating-point - errors that arise during the transformation. - - Args: - U (np.ndarray): 4-by-4 matrix to transform. - reverse (bool): Whether to do the transformation forwards (``B @ U @ B.conj().T``, the - default) or backwards (``B.conj().T @ U @ B``). - - Returns: - np.ndarray: The transformed 4-by-4 matrix. - """ - if reverse: - return _B_nonnormalized_dagger @ U @ _B_nonnormalized - return _B_nonnormalized @ U @ _B_nonnormalized_dagger - - -def weyl_coordinates(U: np.ndarray) -> np.ndarray: - """Computes the Weyl coordinates for a given two-qubit unitary matrix. - - Args: - U (np.ndarray): Input two-qubit unitary. - - Returns: - np.ndarray: Array of the 3 Weyl coordinates. - """ - import scipy.linalg as la - - pi2 = np.pi / 2 - pi4 = np.pi / 4 - - U = U / la.det(U) ** (0.25) - Up = transform_to_magic_basis(U, reverse=True) - # We only need the eigenvalues of `M2 = Up.T @ Up` here, not the full diagonalization. - D = la.eigvals(Up.T @ Up) - d = -np.angle(D) / 2 - d[3] = -d[0] - d[1] - d[2] - cs = np.mod((d[:3] + d[3]) / 2, 2 * np.pi) - - # Reorder the eigenvalues to get in the Weyl chamber - cstemp = np.mod(cs, pi2) - np.minimum(cstemp, pi2 - cstemp, cstemp) - order = np.argsort(cstemp)[[1, 2, 0]] - cs = cs[order] - d[:3] = d[order] - - # Flip into Weyl chamber - if cs[0] > pi2: - cs[0] -= 3 * pi2 - if cs[1] > pi2: - cs[1] -= 3 * pi2 - conjs = 0 - if cs[0] > pi4: - cs[0] = pi2 - cs[0] - conjs += 1 - if cs[1] > pi4: - cs[1] = pi2 - cs[1] - conjs += 1 - if cs[2] > pi2: - cs[2] -= 3 * pi2 - if conjs == 1: - cs[2] = pi2 - cs[2] - if cs[2] > pi4: - cs[2] -= pi2 - - return cs[[1, 0, 2]] diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 73d3cd54c6e7..1772cbd65544 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -11,13 +11,20 @@ # that they have been altered from the originals. """Expand a gate in a circuit using its decomposition rules.""" -from typing import Type, Union, List, Optional + +from __future__ import annotations + +from collections.abc import Sequence +from typing import Type from fnmatch import fnmatch from qiskit.transpiler.basepasses import TransformationPass +from qiskit.dagcircuit.dagnode import DAGOpNode from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.converters.circuit_to_dag import circuit_to_dag -from qiskit.circuit.gate import Gate +from qiskit.circuit.instruction import Instruction + +from ..synthesis import HighLevelSynthesis class Decompose(TransformationPass): @@ -25,16 +32,21 @@ class Decompose(TransformationPass): def __init__( self, - gates_to_decompose: Optional[Union[Type[Gate], List[Type[Gate]], List[str], str]] = None, + gates_to_decompose: ( + str | Type[Instruction] | Sequence[str | Type[Instruction]] | None + ) = None, + apply_synthesis: bool = False, ) -> None: - """Decompose initializer. - + """ Args: gates_to_decompose: optional subset of gates to be decomposed, identified by gate label, name or type. Defaults to all gates. + apply_synthesis: If ``True``, run :class:`.HighLevelSynthesis` to synthesize operations + that do not have a definition attached. """ super().__init__() self.gates_to_decompose = gates_to_decompose + self.apply_synthesis = apply_synthesis def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. @@ -45,13 +57,26 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: output dag where ``gate`` was expanded. """ + # We might use HLS to synthesize objects that do not have a definition + hls = HighLevelSynthesis() if self.apply_synthesis else None + # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): - if self._should_decompose(node): - if getattr(node.op, "definition", None) is None: - continue - # TODO: allow choosing among multiple decomposition rules + # Check in self.gates_to_decompose if the operation should be decomposed + if not self._should_decompose(node): + continue + + if getattr(node.op, "definition", None) is None: + # if we try to synthesize, turn the node into a DAGCircuit and run HLS + if self.apply_synthesis: + node_as_dag = _node_to_dag(node) + synthesized = hls.run(node_as_dag) + dag.substitute_node_with_dag(node, synthesized) + + # else: no definition and synthesis not enabled, so we do nothing + else: rule = node.op.definition.data + if ( len(rule) == 1 and len(node.qargs) == len(rule[0].qubits) == 1 # to preserve gate order @@ -66,9 +91,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: return dag - def _should_decompose(self, node) -> bool: - """Call a decomposition pass on this circuit, - to decompose one level (shallow decompose).""" + def _should_decompose(self, node: DAGOpNode) -> bool: + """Call a decomposition pass on this circuit to decompose one level (shallow decompose).""" if self.gates_to_decompose is None: # check if no gates given return True @@ -96,3 +120,12 @@ def _should_decompose(self, node) -> bool: return True else: return False + + +def _node_to_dag(node: DAGOpNode) -> DAGCircuit: + dag = DAGCircuit() + dag.add_qubits(node.qargs) + dag.add_clbits(node.cargs) + + dag.apply_operation_back(node.op, node.qargs, node.cargs) + return dag diff --git a/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml b/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml index 695461a58160..689c5e4623b0 100644 --- a/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml +++ b/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml @@ -9,3 +9,20 @@ features_circuits: a :class:`.QuantumCircuit` object instead of building a subclass object. The second is that this new function is multithreaded and implemented in rust so it generates the output circuit ~10x faster than the :class:`.QuantumVolume` class. + - | + Improved the runtime performance of constructing the + :class:`.QuantumVolume` class with the ``classical_permutation`` argument set + to ``True``. Internally it now calls the :func:`.quantum_volume` + function which is written in Rust which is ~10x faster to generate a + quantum volume circuit. + +upgrade_circuits: + - | + The :class:`.QuantumVolume` class will generate circuits with + different unitary matrices and permutations for a given seed value from + the previous Qiskit release. This is due to using a new internal random + number generator for the circuit generation that will generate the circuit + more quickly. If you need an exact circuit with the same seed you can + use the previous release of Qiskit and generate the circuit with the + ``flatten=True`` argument and export the circuit with :func:`.qpy.dump` + and then load it with this release. diff --git a/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml b/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml new file mode 100644 index 000000000000..c5b119c2534f --- /dev/null +++ b/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml @@ -0,0 +1,6 @@ +--- +features_circuits: + - | + Specialized implementations of :meth:`~object.__eq__` have been added for all standard-library circuit gates. + Most of the standard gates already specialized this method, but a few did not, and could cause significant + slowdowns in unexpected places. diff --git a/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml b/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml new file mode 100644 index 000000000000..f6161ea72f80 --- /dev/null +++ b/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml @@ -0,0 +1,42 @@ +--- +features_circuits: + - | + Added a new argument ``"apply_synthesis"`` to :class:`.Decompose`, which allows + the transpiler pass to apply high-level synthesis to decompose objects that are only + defined by a synthesis routine. For example:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford + from qiskit.transpiler.passes import Decompose + + cliff = Clifford(HGate()) + circuit = QuantumCircuit(1) + circuit.append(cliff, [0]) + + # Clifford has no .definition, it is only defined by synthesis + nothing_happened = Decompose()(circuit) + + # this internally runs the HighLevelSynthesis pass to decompose the Clifford + decomposed = Decompose(apply_synthesis=True)(circuit) + +fixes: + - | + Fixed a bug in :meth:`.QuantumCircuit.decompose` where objects that could be synthesized + with :class:`.HighLevelSynthesis` were first synthesized and then decomposed immediately + (i.e., they were decomposed twice instead of once). This affected, e.g., :class:`.MCXGate` + or :class:`.Clifford`, among others. + - | + Fixed a bug in :meth:`.QuantumCircuit.decompose`, where high-level objects without a definition + were not decomposed if they were explicitly set via the ``"gates_to_decompose"`` argument. + For example, previously the following did not perform a decomposition but now works as + expected:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford + from qiskit.transpiler.passes import Decompose + + cliff = Clifford(HGate()) + circuit = QuantumCircuit(1) + circuit.append(cliff, [0]) + + decomposed = Decompose(gates_to_decompose=["clifford"])(circuit) diff --git a/test/python/circuit/library/test_mcmt.py b/test/python/circuit/library/test_mcmt.py index c7903602b6e2..ead6a07d8b4d 100644 --- a/test/python/circuit/library/test_mcmt.py +++ b/test/python/circuit/library/test_mcmt.py @@ -18,13 +18,14 @@ from qiskit.exceptions import QiskitError from qiskit.compiler import transpile -from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit import QuantumCircuit, QuantumRegister, Parameter from qiskit.circuit.library import ( MCMT, MCMTVChain, CHGate, XGate, ZGate, + RYGate, CXGate, CZGate, MCMTGate, @@ -264,6 +265,24 @@ def test_invalid_base_gate_width(self): with self.assertRaises(ValueError): _ = MCMTGate(gate, 10, 2) + def test_invalid_base_gate_width_synthfun(self): + """Test only 1-qubit base gates are accepted.""" + for gate in [GlobalPhaseGate(0.2), SwapGate()]: + with self.subTest(gate=gate): + with self.assertRaises(ValueError): + _ = synth_mcmt_vchain(gate, 10, 2) + + def test_gate_with_parameters_vchain(self): + """Test a gate with parameters as base gate.""" + theta = Parameter("th") + gate = RYGate(theta) + num_target = 3 + circuit = synth_mcmt_vchain(gate, num_ctrl_qubits=10, num_target_qubits=num_target) + + self.assertEqual(circuit.count_ops().get("cry", 0), num_target) + self.assertEqual(circuit.num_parameters, 1) + self.assertIs(circuit.parameters[0], theta) + if __name__ == "__main__": unittest.main() diff --git a/test/python/synthesis/test_weyl.py b/test/python/synthesis/test_weyl.py index f0773af205ef..a67d553b7851 100644 --- a/test/python/synthesis/test_weyl.py +++ b/test/python/synthesis/test_weyl.py @@ -17,8 +17,8 @@ import numpy as np from numpy.testing import assert_allclose +from qiskit._accelerate.two_qubit_decompose import weyl_coordinates from qiskit.quantum_info.random import random_unitary -from qiskit.synthesis.two_qubit.weyl import weyl_coordinates from qiskit.synthesis.two_qubit.local_invariance import ( two_qubit_local_invariants, local_equivalence, @@ -32,7 +32,7 @@ class TestWeyl(QiskitTestCase): def test_weyl_coordinates_simple(self): """Check Weyl coordinates against known cases.""" # Identity [0,0,0] - U = np.identity(4) + U = np.identity(4, dtype=complex) weyl = weyl_coordinates(U) assert_allclose(weyl, [0, 0, 0]) diff --git a/test/python/synthesis/xx_decompose/test_circuits.py b/test/python/synthesis/xx_decompose/test_circuits.py index 3c9aef8c7464..5e89f5c08420 100644 --- a/test/python/synthesis/xx_decompose/test_circuits.py +++ b/test/python/synthesis/xx_decompose/test_circuits.py @@ -20,10 +20,10 @@ import ddt import numpy as np +from qiskit._accelerate.two_qubit_decompose import weyl_coordinates from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import RZGate, UnitaryGate import qiskit.quantum_info.operators -from qiskit.synthesis.two_qubit.weyl import weyl_coordinates from qiskit.synthesis.two_qubit.xx_decompose.circuits import ( decompose_xxyy_into_xxyy_xx, xx_circuit_step, diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 1223b37ca3ff..64f08ec52682 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passes import Decompose from qiskit.converters import circuit_to_dag from qiskit.circuit.library import HGate, CCXGate, U2Gate -from qiskit.quantum_info.operators import Operator +from qiskit.quantum_info.operators import Operator, Clifford from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -317,3 +317,34 @@ def test_decompose_single_qubit_clbit(self): decomposed = circuit.decompose() self.assertEqual(decomposed, block) + + def test_decompose_synthesis(self): + """Test a high-level object with only a synthesis and no definition is correctly decomposed.""" + qc = QuantumCircuit(1) + qc.h(0) + cliff = Clifford(qc) + + bigger = QuantumCircuit(1) + bigger.append(cliff, [0]) + + decomposed = bigger.decompose() + + self.assertEqual(qc, decomposed) + + def test_specify_hls_object(self): + """Test specifying an HLS object by name works.""" + qc = QuantumCircuit(1) + qc.h(0) + cliff = Clifford(qc) + + bigger = QuantumCircuit(1) + bigger.append(cliff, [0]) + bigger.h(0) # add another gate that should remain unaffected, but has a definition + + decomposed = bigger.decompose(gates_to_decompose=["clifford"]) + + expected = QuantumCircuit(1) + expected.h(0) + expected.h(0) + + self.assertEqual(expected, decomposed)