From 549edb1c4fbaebbbc9a8e93384fb73d6a74d157d Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 11 Jan 2024 13:47:23 +0000 Subject: [PATCH] Begin work on new OpenQASM 3 importer --- Cargo.lock | 606 +++++++++++++++++++++++++++- crates/qasm3/Cargo.toml | 20 + crates/qasm3/README.md | 4 + crates/qasm3/src/build.rs | 330 +++++++++++++++ crates/qasm3/src/circuit.rs | 309 ++++++++++++++ crates/qasm3/src/error.rs | 15 + crates/qasm3/src/expr.rs | 258 ++++++++++++ crates/qasm3/src/lib.rs | 129 ++++++ qiskit/qasm/libs/dummy/stdgates.inc | 75 ++++ qiskit/qasm3/exporter.py | 2 +- setup.py | 6 + 11 files changed, 1740 insertions(+), 14 deletions(-) create mode 100644 crates/qasm3/Cargo.toml create mode 100644 crates/qasm3/README.md create mode 100644 crates/qasm3/src/build.rs create mode 100644 crates/qasm3/src/circuit.rs create mode 100644 crates/qasm3/src/error.rs create mode 100644 crates/qasm3/src/expr.rs create mode 100644 crates/qasm3/src/lib.rs create mode 100644 qiskit/qasm/libs/dummy/stdgates.inc diff --git a/Cargo.lock b/Cargo.lock index 0fc7200d6616..53e7d612d41f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,26 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "always-assert" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4436e0292ab1bb631b42973c61205e704475fe8126af845c8d923c0996328127" +dependencies = [ + "log", +] + +[[package]] +name = "ariadne" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" +dependencies = [ + "concolor", + "unicode-width", + "yansi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -33,12 +53,70 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "boolenum" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c8abd585d7026df20a9ae12982127ba5e81cc7a09397b957e71659da8c5de8" +dependencies = [ + "proc-macro-error", + "quote", + "syn 1.0.109", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concolor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" +dependencies = [ + "bitflags 1.3.2", + "concolor-query", + "is-terminal", +] + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "cov-mark" +version = "2.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a" + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.4" @@ -70,6 +148,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "drop_bomb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" + [[package]] name = "either" version = "1.9.0" @@ -82,6 +166,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -122,6 +216,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "indexmap" version = "1.9.3" @@ -149,6 +249,17 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -158,6 +269,12 @@ dependencies = [ "either", ] +[[package]] +name = "jod-thread" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae" + [[package]] name = "libc" version = "0.2.151" @@ -170,6 +287,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "lock_api" version = "0.4.11" @@ -180,6 +303,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "matrixmultiply" version = "0.3.8" @@ -199,6 +328,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "ndarray" version = "0.15.6" @@ -268,12 +406,79 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oq3_lexer" +version = "0.0.1" +source = "git+https://github.com/Qiskit/openqasm3_parser.git?rev=57407ee#57407ee9504a8e251eb476c054e990e0f686822a" +dependencies = [ + "unicode-properties", + "unicode-xid", +] + +[[package]] +name = "oq3_parser" +version = "0.0.1" +source = "git+https://github.com/Qiskit/openqasm3_parser.git?rev=57407ee#57407ee9504a8e251eb476c054e990e0f686822a" +dependencies = [ + "drop_bomb", + "oq3_lexer", + "ra_ap_limit", +] + +[[package]] +name = "oq3_semantics" +version = "0.0.1" +source = "git+https://github.com/Qiskit/openqasm3_parser.git?rev=57407ee#57407ee9504a8e251eb476c054e990e0f686822a" +dependencies = [ + "boolenum", + "hashbrown 0.14.3", + "oq3_source_file", + "oq3_syntax", + "rowan", +] + +[[package]] +name = "oq3_source_file" +version = "0.0.1" +source = "git+https://github.com/Qiskit/openqasm3_parser.git?rev=57407ee#57407ee9504a8e251eb476c054e990e0f686822a" +dependencies = [ + "ariadne", + "oq3_syntax", + "source-span", +] + +[[package]] +name = "oq3_syntax" +version = "0.0.1" +source = "git+https://github.com/Qiskit/openqasm3_parser.git?rev=57407ee#57407ee9504a8e251eb476c054e990e0f686822a" +dependencies = [ + "cov-mark", + "either", + "indexmap 2.1.0", + "itertools", + "once_cell", + "oq3_lexer", + "oq3_parser", + "ra_ap_stdx", + "rowan", + "rustc-hash", + "smol_str", + "triomphe", + "xshell", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -292,9 +497,9 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -323,6 +528,30 @@ dependencies = [ "indexmap 1.9.3", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.75" @@ -382,7 +611,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -394,7 +623,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -405,6 +634,15 @@ dependencies = [ "pyo3", ] +[[package]] +name = "qiskit-qasm3" +version = "1.0.0" +dependencies = [ + "hashbrown 0.14.3", + "oq3_semantics", + "pyo3", +] + [[package]] name = "qiskit_accelerate" version = "1.0.0" @@ -435,6 +673,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ra_ap_limit" +version = "0.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d33758724f997689f84146e5401e28d875a061804f861f113696f44f5232aa" + +[[package]] +name = "ra_ap_stdx" +version = "0.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e80fb2ff88b31fa35cde89ae13ea7c9ada97c7a2c778dcafef530a267658000" +dependencies = [ + "always-assert", + "crossbeam-channel", + "jod-thread", + "libc", + "miow", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -521,13 +779,41 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "rowan" +version = "0.15.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" +dependencies = [ + "countme", + "hashbrown 0.14.3", + "memoffset", + "rustc-hash", + "text-size", ] [[package]] @@ -536,6 +822,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustworkx-core" version = "0.13.2" @@ -561,12 +860,61 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "smallvec" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + +[[package]] +name = "source-span" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3afeb8b7a1ae2e8721f2193cc2291c9e00dd68511907fd8b807e2c8d42caa3c" +dependencies = [ + "termion", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.48" @@ -584,12 +932,54 @@ version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall 0.2.16", + "redox_termios", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-properties" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "unindent" version = "0.2.3" @@ -608,63 +998,253 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "xshell" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" @@ -682,5 +1262,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] diff --git a/crates/qasm3/Cargo.toml b/crates/qasm3/Cargo.toml new file mode 100644 index 000000000000..9ae9014e6aba --- /dev/null +++ b/crates/qasm3/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "qiskit-qasm3" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lib] +name = "qiskit_qasm3" +crate-type = ["cdylib"] + +[features] +# This is a test-only shim removable feature. See the root `Cargo.toml`. +default = ["extension-module"] +extension-module = ["pyo3/extension-module"] + +[dependencies] +pyo3.workspace = true +hashbrown.version = "0.14.0" +oq3_semantics = { git = "https://github.com/Qiskit/openqasm3_parser.git", rev = "57407ee" } diff --git a/crates/qasm3/README.md b/crates/qasm3/README.md new file mode 100644 index 000000000000..f8dc1623021e --- /dev/null +++ b/crates/qasm3/README.md @@ -0,0 +1,4 @@ +# `qiskit._qasm3` + +This crate is the Rust-level Qiskit interface to an OpenQASM 3 parser. The parser itself does not know +about Qiskit, and this crate interfaces with it in a Qiskit-specific manner to produce `QuantumCircuit`s. diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs new file mode 100644 index 000000000000..d1b8386f8497 --- /dev/null +++ b/crates/qasm3/src/build.rs @@ -0,0 +1,330 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::prelude::*; +use pyo3::types::{PyString, PyTuple}; + +use hashbrown::HashMap; + +use oq3_semantics::asg; +use oq3_semantics::symbols::{SymbolId, SymbolTable, SymbolType}; +use oq3_semantics::types::{ArrayDims, Type}; + +use crate::circuit::{PyCircuit, PyCircuitModule, PyClassicalRegister, PyGate, PyQuantumRegister}; +use crate::error::QASM3ImporterError; +use crate::expr; + +/// Our internal symbol table mapping base symbols to the Python-space object that represents them. +#[derive(Default)] +pub struct PySymbolTable { + /// Gate-constructor objects. + pub gates: HashMap, + /// Scalar `Qubit` objects. + pub qubits: HashMap>, + /// Scalar `Clbit` objects. + pub clbits: HashMap>, + /// `QuantumRegister` objects. + pub qregs: HashMap, + /// `ClassicalRegister` objects. + pub cregs: HashMap, +} + +struct BuilderState { + /// The base circuit under construction. + qc: PyCircuit, + /// Symbol table mapping AST symbols into typed Python / Rust objects. This is owned state; we + /// mutate it and build it up as we parse the AST. + symbols: PySymbolTable, + /// Handle to the constructor object for Python-space objects. + module: PyCircuitModule, + /// Constructors for gate objects. + pygates: HashMap, +} + +impl BuilderState { + fn declare_classical( + &mut self, + py: Python, + ast_symbols: &SymbolTable, + decl: &asg::DeclareClassical, + ) -> PyResult<()> { + let name_id = decl + .name() + .as_ref() + .map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?; + let name_symbol = &ast_symbols[name_id]; + match name_symbol.symbol_type() { + Type::Bit(is_const) => { + if is_const.clone().into() { + Err(QASM3ImporterError::new_err("cannot handle consts")) + } else if decl.initializer().is_some() { + Err(QASM3ImporterError::new_err( + "cannot handle initialised bits", + )) + } else { + self.add_clbit(py, name_id.clone()) + } + } + Type::BitArray(dims, is_const) => { + if is_const.clone().into() { + Err(QASM3ImporterError::new_err("cannot handle consts")) + } else if decl.initializer().is_some() { + Err(QASM3ImporterError::new_err( + "cannot handle initialised registers", + )) + } else { + match dims { + ArrayDims::D1(size) => { + self.add_creg(py, name_id.clone(), name_symbol.name(), *size) + } + _ => Err(QASM3ImporterError::new_err( + "cannot handle quantum registers with more than one dimension", + )), + } + } + } + ty => Err(QASM3ImporterError::new_err(format!( + "unhandled classical type: {:?}", + ty, + ))), + } + } + + fn declare_quantum( + &mut self, + py: Python, + ast_symbols: &SymbolTable, + decl: &asg::DeclareQuantum, + ) -> PyResult<()> { + let name_id = decl + .name() + .as_ref() + .map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?; + let name_symbol = &ast_symbols[name_id]; + match name_symbol.symbol_type() { + Type::Qubit => self.add_qubit(py, name_id.clone()), + Type::QubitArray(dims) => match dims { + ArrayDims::D1(size) => { + self.add_qreg(py, name_id.clone(), name_symbol.name(), *size) + } + _ => Err(QASM3ImporterError::new_err( + "cannot handle quantum registers with more than one dimension", + )), + }, + _ => unreachable!(), + } + } + + fn call_gate( + &mut self, + py: Python, + ast_symbols: &SymbolTable, + call: &asg::GateCall, + ) -> PyResult<()> { + if call.modifier().is_some() { + return Err(QASM3ImporterError::new_err("gate modifiers not handled")); + } + let gate_id = call + .name() + .as_ref() + .map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?; + let gate = self.symbols.gates.get(gate_id).ok_or_else(|| { + QASM3ImporterError::new_err(format!("internal logic error: unknown gate {:?}", gate_id)) + })?; + let params = PyTuple::new( + py, + call.params() + .as_ref() + .map(|params| params as &[asg::TExpr]) + .unwrap_or_default() + .iter() + .map(|param| expr::eval_gate_param(py, &self.symbols, ast_symbols, param)) + .collect::>>()?, + ); + let qargs = call.qubits(); + if params.len() != gate.num_params() { + return Err(QASM3ImporterError::new_err(format!( + "incorrect number of params to '{}': expected {}, got {}", + gate.name(), + gate.num_params(), + params.len(), + ))); + } + if qargs.len() != gate.num_qubits() { + return Err(QASM3ImporterError::new_err(format!( + "incorrect number of quantum arguments to '{}': expected {}, got {}", + gate.name(), + gate.num_qubits(), + qargs.len(), + ))); + } + let gate_instance = gate.construct(py, params)?; + for qubits in expr::broadcast_qubits(py, &self.symbols, ast_symbols, qargs)? { + self.qc.append( + py, + self.module + .new_instruction(py, gate_instance.clone_ref(py), qubits, ())?, + )?; + } + Ok(()) + } + + fn define_gate( + &mut self, + _py: Python, + ast_symbols: &SymbolTable, + decl: &asg::GateDeclaration, + ) -> PyResult<()> { + let name_id = decl + .name() + .as_ref() + .map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?; + let name_symbol = &ast_symbols[name_id]; + let pygate = self.pygates.get(name_symbol.name()).ok_or_else(|| { + QASM3ImporterError::new_err(format!( + "can't handle non-built-in gate: '{}'", + name_symbol.name() + )) + })?; + let defined_num_params = decl.params().as_ref().map_or(0, Vec::len); + let defined_num_qubits = decl.qubits().len(); + if pygate.num_params() != defined_num_params { + return Err(QASM3ImporterError::new_err(format!( + "given constructor for '{}' expects {} parameters, but is defined as taking {}", + pygate.name(), + pygate.num_params(), + defined_num_params, + ))); + } + if pygate.num_qubits() != defined_num_qubits { + return Err(QASM3ImporterError::new_err(format!( + "given constructor for '{}' expects {} qubits, but is defined as taking {}", + pygate.name(), + pygate.num_qubits(), + defined_num_qubits, + ))); + } + self.symbols.gates.insert(name_id.clone(), pygate.clone()); + Ok(()) + } + + fn add_qubit(&mut self, py: Python, ast_symbol: SymbolId) -> PyResult<()> { + let qubit = self.module.new_qubit(py)?; + if self + .symbols + .qubits + .insert(ast_symbol, qubit.clone_ref(py)) + .is_some() + { + panic!("internal logic error: attempted to add the same qubit multiple times") + } + self.qc.add_qubit(py, qubit) + } + + fn add_clbit(&mut self, py: Python, ast_symbol: SymbolId) -> PyResult<()> { + let clbit = self.module.new_clbit(py)?; + if self + .symbols + .clbits + .insert(ast_symbol, clbit.clone_ref(py)) + .is_some() + { + panic!("internal logic error: attempted to add the same clbit multiple times") + } + self.qc.add_clbit(py, clbit) + } + + fn add_qreg>>( + &mut self, + py: Python, + ast_symbol: SymbolId, + name: T, + size: usize, + ) -> PyResult<()> { + let qreg = self.module.new_qreg(py, name, size)?; + self.qc.add_qreg(py, &qreg)?; + if self.symbols.qregs.insert(ast_symbol, qreg).is_some() { + panic!("internal logic error: attempted to add the same register multiple times") + } + Ok(()) + } + + fn add_creg>>( + &mut self, + py: Python, + ast_symbol: SymbolId, + name: T, + size: usize, + ) -> PyResult<()> { + let creg = self.module.new_creg(py, name, size)?; + self.qc.add_creg(py, &creg)?; + if self.symbols.cregs.insert(ast_symbol, creg).is_some() { + panic!("internal logic error: attempted to add the same register multiple times") + } + Ok(()) + } +} + +pub fn convert_asg( + py: Python, + program: &asg::Program, + ast_symbols: &SymbolTable, + gate_constructors: HashMap, +) -> PyResult { + let module = PyCircuitModule::import(py)?; + let mut state = BuilderState { + qc: module.new_circuit(py)?, + symbols: Default::default(), + pygates: gate_constructors, + module, + }; + for statement in program.stmts().iter() { + match statement { + asg::Stmt::DeclareClassical(decl) => state.declare_classical(py, ast_symbols, decl)?, + asg::Stmt::DeclareQuantum(decl) => state.declare_quantum(py, ast_symbols, decl)?, + asg::Stmt::GateCall(call) => state.call_gate(py, ast_symbols, call)?, + asg::Stmt::GateDeclaration(decl) => state.define_gate(py, ast_symbols, decl)?, + asg::Stmt::Alias + | asg::Stmt::AnnotatedStmt(_) + | asg::Stmt::Assignment(_) + | asg::Stmt::Barrier + | asg::Stmt::Block(_) + | asg::Stmt::Box + | asg::Stmt::Break + | asg::Stmt::Cal + | asg::Stmt::Continue + | asg::Stmt::Def + | asg::Stmt::DefCal + | asg::Stmt::Delay + | asg::Stmt::End + | asg::Stmt::ExprStmt(_) + | asg::Stmt::Extern + | asg::Stmt::For + | asg::Stmt::GPhaseCall(_) + | asg::Stmt::IODeclaration + | asg::Stmt::If(_) + | asg::Stmt::Include(_) + | asg::Stmt::NullStmt + | asg::Stmt::OldStyleDeclaration + | asg::Stmt::Pragma(_) + | asg::Stmt::Reset + | asg::Stmt::Return + | asg::Stmt::While(_) => { + return Err(QASM3ImporterError::new_err(format!( + "this statement is not yet handled during OpenQASM 3 import: {:?}", + statement + ))); + } + } + } + Ok(state.qc) +} diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs new file mode 100644 index 000000000000..20720233b2e9 --- /dev/null +++ b/crates/qasm3/src/circuit.rs @@ -0,0 +1,309 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::prelude::*; +use pyo3::types::{PyList, PyString, PyTuple, PyType}; + +use crate::error::QASM3ImporterError; + +macro_rules! register_type { + ($name: ident) => { + /// Rust-space wrapper around Qiskit `Register` objects. + pub struct $name { + /// The actual register instance. + object: Py, + /// A pointer to the inner list of bits. We keep a handle to this for lookup + /// efficiency; we can use direct list methods to retrieve the bit instances, rather + /// than needing to indirect through the general `__getitem__` of the register, or + /// looking up the qubit instances on the circuit. + items: Py, + } + + impl $name { + /// Get an individual bit from the register. + pub fn bit(&self, py: Python, index: usize) -> PyResult> { + // Unfortunately, `PyList::get_item_unchecked` isn't usable with the stable ABI. + self.items + .as_ref(py) + .get_item(index) + .map(|item| item.into_py(py)) + } + + pub fn iter<'a>(&'a self, py: Python<'a>) -> impl Iterator { + self.items.as_ref(py).iter() + } + } + + impl ::pyo3::IntoPy> for $name { + fn into_py(self, _py: Python) -> Py { + self.object + } + } + + impl ::pyo3::ToPyObject for $name { + fn to_object(&self, _py: Python) -> Py { + // _Technically_, allowing access this internal object can let the Rust-space + // wrapper get out-of-sync since we keep a direct handle to the list, but in + // practice, the field it's viewing is private and "inaccessible" from Python. + self.object.clone() + } + } + }; +} + +register_type!(PyQuantumRegister); +register_type!(PyClassicalRegister); + +/// Information received from Python space about how to construct a Python-space object to +/// represent a given gate that might be declared. +#[pyclass(module = "qiskit._qasm3", frozen, name = "CustomGate")] +#[derive(Clone, Debug)] +pub struct PyGate { + constructor: Py, + name: String, + num_params: usize, + num_qubits: usize, +} + +impl PyGate { + pub fn new>>( + py: Python, + constructor: T, + name: String, + num_params: usize, + num_qubits: usize, + ) -> Self { + Self { + constructor: constructor.into_py(py), + name, + num_params, + num_qubits, + } + } + + /// Construct a Python-space instance of the custom gate. + pub fn construct(&self, py: Python, args: A) -> PyResult> + where + A: IntoPy>, + { + let args = args.into_py(py); + let received_num_params = args.as_ref(py).len(); + if received_num_params == self.num_params { + self.constructor.call1(py, args.as_ref(py)) + } else { + Err(QASM3ImporterError::new_err(format!( + "internal logic error: wrong number of params for {} (got {}, expected {})", + &self.name, received_num_params, self.num_params + ))) + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn num_params(&self) -> usize { + self.num_params + } + + pub fn num_qubits(&self) -> usize { + self.num_qubits + } +} + +#[pymethods] +impl PyGate { + #[new] + #[pyo3(signature=(/, constructor, name, num_params, num_qubits))] + fn __new__(constructor: Py, name: String, num_params: usize, num_qubits: usize) -> Self { + Self { + constructor, + name, + num_params, + num_qubits, + } + } + + fn __repr__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + PyString::new(py, "CustomGate(name={!r}, num_params={}, num_qubits={})").call_method1( + "format", + ( + PyString::new(py, &self.name), + self.num_params, + self.num_qubits, + ), + ) + } + + fn __reduce__(&self, py: Python) -> Py { + ( + PyType::new::(py), + ( + self.constructor.clone_ref(py), + &self.name, + self.num_params, + self.num_qubits, + ), + ) + .into_py(py) + } +} + +/// Wrapper around various Python-space imports. This is just a convenience wrapper to save us +/// needing to `getattr` things off a Python-space module quite so frequently. This is +/// give-or-take just a manual lookup for a few `import` items at the top of a Python module, and +/// the attached constructor functions produce (when appropriate), Rust-space wrappers around the +/// Python objects. +pub struct PyCircuitModule { + circuit: Py, + qreg: Py, + qubit: Py, + creg: Py, + clbit: Py, + instruction: Py, +} + +impl PyCircuitModule { + /// Import the necessary components from `qiskit.circuit`. + pub fn import(py: Python) -> PyResult { + let module = PyModule::import(py, "qiskit.circuit")?; + Ok(Self { + circuit: module + .getattr("QuantumCircuit")? + .downcast::()? + .into_py(py), + qreg: module + .getattr("QuantumRegister")? + .downcast::()? + .into_py(py), + qubit: module.getattr("Qubit")?.downcast::()?.into_py(py), + creg: module + .getattr("ClassicalRegister")? + .downcast::()? + .into_py(py), + clbit: module.getattr("Clbit")?.downcast::()?.into_py(py), + instruction: module + .getattr("CircuitInstruction")? + .downcast::()? + .into_py(py), + }) + } + + pub fn new_circuit(&self, py: Python) -> PyResult { + Ok(PyCircuit { + qc: self.circuit.call0(py)?, + }) + } + + pub fn new_qreg>>( + &self, + py: Python, + name: T, + size: usize, + ) -> PyResult { + let qreg = self.qreg.call1(py, (size, name.into_py(py)))?; + Ok(PyQuantumRegister { + items: qreg + .getattr(py, "_bits")? + .downcast::(py)? + .into_py(py), + object: qreg, + }) + } + + pub fn new_qubit(&self, py: Python) -> PyResult> { + self.qubit.call0(py) + } + + pub fn new_creg>>( + &self, + py: Python, + name: T, + size: usize, + ) -> PyResult { + let creg = self.creg.call1(py, (size, name.into_py(py)))?; + Ok(PyClassicalRegister { + items: creg + .getattr(py, "_bits")? + .downcast::(py)? + .into_py(py), + object: creg, + }) + } + + pub fn new_clbit(&self, py: Python) -> PyResult> { + self.clbit.call0(py) + } + + pub fn new_instruction( + &self, + py: Python, + operation: O, + qubits: Q, + clbits: C, + ) -> PyResult> + where + O: IntoPy>, + Q: IntoPy>, + C: IntoPy>, + { + self.instruction + .call1(py, (operation, qubits.into_py(py), clbits.into_py(py))) + } +} + +/// Circuit construction context object to provide an easier Rust-space interface for us to +/// construct the Python :class:`.QuantumCircuit`. The idea of doing this from Rust space like +/// this is that we might steadily be able to move more and more of it into being native Rust as +/// the Rust-space APIs around the internal circuit data stabilise. +pub struct PyCircuit { + /// The actual circuit object that's under construction. + qc: Py, +} + +impl PyCircuit { + pub fn add_qreg(&mut self, py: Python, qreg: &PyQuantumRegister) -> PyResult<()> { + self.qc + .call_method1(py, "add_register", (qreg.to_object(py),)) + .map(|_| ()) + } + + pub fn add_qubit(&mut self, py: Python, qubit: Py) -> PyResult<()> { + self.qc + .call_method1(py, "add_bits", ((qubit,),)) + .map(|_| ()) + } + + pub fn add_creg(&mut self, py: Python, creg: &PyClassicalRegister) -> PyResult<()> { + self.qc + .call_method1(py, "add_register", (creg.to_object(py),)) + .map(|_| ()) + } + + pub fn add_clbit>>(&mut self, py: Python, clbit: T) -> PyResult<()> { + self.qc + .call_method1(py, "add_bits", ((clbit,),)) + .map(|_| ()) + } + + pub fn append>>(&mut self, py: Python, instruction: T) -> PyResult<()> { + self.qc + .call_method1(py, "_append", (instruction.into_py(py),)) + .map(|_| ()) + } +} + +impl ::pyo3::IntoPy> for PyCircuit { + fn into_py(self, py: Python) -> Py { + self.qc.clone_ref(py) + } +} diff --git a/crates/qasm3/src/error.rs b/crates/qasm3/src/error.rs new file mode 100644 index 000000000000..1bde455f3a36 --- /dev/null +++ b/crates/qasm3/src/error.rs @@ -0,0 +1,15 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::import_exception; + +import_exception!(qiskit.qasm3.exceptions, QASM3ImporterError); diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs new file mode 100644 index 000000000000..7f587cb6bd22 --- /dev/null +++ b/crates/qasm3/src/expr.rs @@ -0,0 +1,258 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::prelude::*; +use pyo3::types::PyTuple; + +use oq3_semantics::asg; +use oq3_semantics::symbols::{SymbolId, SymbolTable}; +use oq3_semantics::types::Type; + +use crate::build::PySymbolTable; +use crate::error::QASM3ImporterError; + +pub fn eval_gate_param( + _py: Python, + _our_symbols: &PySymbolTable, + _ast_symbols: &SymbolTable, + param: &asg::TExpr, +) -> PyResult { + // Only handling float parameters in this first pass of the importer. + match param.get_type() { + Type::Float(_, is_const) => { + if is_const.clone().into() { + match param.expression() { + asg::Expr::Literal(asg::Literal::Float(lit)) => { + lit.value().parse().map_err(|_| { + QASM3ImporterError::new_err(format!( + "invalid float literal: '{}'", + lit.value() + )) + }) + } + expr => Err(QASM3ImporterError::new_err(format!( + "unhandled expression for floating-point constant: {:?}", + expr + ))), + } + } else { + Err(QASM3ImporterError::new_err(format!( + "expected a constant float, but found a runtime value: {:?}", + param + ))) + } + } + ty => Err(QASM3ImporterError::new_err(format!( + "expected an angle-like type, but saw {:?}", + ty + ))), + } +} + +fn eval_const_int(_py: Python, _ast_symbols: &SymbolTable, expr: &asg::TExpr) -> PyResult { + match expr.get_type() { + Type::Int(_, is_const) | Type::UInt(_, is_const) => { + if is_const.clone().into() { + match expr.expression() { + asg::Expr::Literal(asg::Literal::Int(lit)) => Ok(*lit.value() as isize), + expr => Err(QASM3ImporterError::new_err(format!( + "unhandled expression type for constant-integer evaluatation: {:?}", + expr + ))), + } + } else { + Err(QASM3ImporterError::new_err(format!( + "expected a constant integer, but found a runtime value: {:?}", + expr + ))) + } + } + ty => Err(QASM3ImporterError::new_err(format!( + "expected a constant integer, but found a value of type: {:?}", + ty + ))), + } +} + +fn eval_const_uint(py: Python, ast_symbols: &SymbolTable, expr: &asg::TExpr) -> PyResult { + eval_const_int(py, ast_symbols, expr).and_then(|val| { + val.try_into().map_err(|_| { + QASM3ImporterError::new_err(format!("expected an unsigned integer but found '{}'", val)) + }) + }) +} + +enum BroadcastItem { + Bit(Py), + Register(Vec>), +} + +struct BroadcastQubitsIter<'py> { + py: Python<'py>, + len: usize, + offset: usize, + items: Vec, +} + +impl<'py> Iterator for BroadcastQubitsIter<'py> { + type Item = &'py PyTuple; + + fn next(&mut self) -> Option { + if self.offset >= self.len { + return None; + } + let offset = self.offset; + let to_scalar = |item: &BroadcastItem| match item { + BroadcastItem::Bit(bit) => bit.clone(), + BroadcastItem::Register(bits) => bits[offset].clone(), + }; + self.offset += 1; + Some(PyTuple::new(self.py, self.items.iter().map(to_scalar))) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len - self.offset, Some(self.len - self.offset)) + } +} + +fn broadcast_bits_for_identifier( + py: Python, + our_symbols: &PySymbolTable, + iden_symbol: &SymbolId, +) -> PyResult { + if let Some(bit) = our_symbols.qubits.get(iden_symbol) { + Ok(BroadcastItem::Bit(bit.clone())) + } else if let Some(reg) = our_symbols.qregs.get(iden_symbol) { + Ok(BroadcastItem::Register( + reg.iter(py).map(|obj| obj.into_py(py)).collect(), + )) + } else { + Err(QASM3ImporterError::new_err(format!( + "unknown symbol: {:?}", + iden_symbol + ))) + } +} + +fn broadcast_apply_index( + py: Python, + ast_symbols: &SymbolTable, + broadcasted: BroadcastItem, + index: &asg::IndexOperator, +) -> PyResult { + let bits = match broadcasted { + BroadcastItem::Register(bits) => Ok(bits), + BroadcastItem::Bit(_) => Err(QASM3ImporterError::new_err( + "cannot index into a scalar value", + )), + }?; + let eval_single_index = |expr: &asg::TExpr| -> PyResult> { + let index = eval_const_uint(py, ast_symbols, expr)?; + match bits.get(index) { + Some(bit) => Ok(bit.clone_ref(py)), + None => Err(QASM3ImporterError::new_err(format!( + "index {} out of range for register of length {}", + index, + bits.len() + ))), + } + }; + match index { + asg::IndexOperator::SetExpression(exprs) => exprs + .expressions() + .iter() + .map(eval_single_index) + .collect::>>() + .map(BroadcastItem::Register), + asg::IndexOperator::ExpressionList(exprs) => { + let expr = match &exprs.expressions[..] { + [expr] => Ok(expr), + _ => Err(QASM3ImporterError::new_err( + "registers can only be one-dimensional", + )), + }?; + match expr.get_type() { + Type::UInt(_, _) | Type::Int(_, _) => { + Ok(BroadcastItem::Bit(eval_single_index(expr)?)) + } + ty => Err(QASM3ImporterError::new_err(format!( + "unhandled index type: {:?}", + ty + ))), + } + } + } +} + +pub fn broadcast_qubits<'a, 'py, T>( + py: Python<'py>, + our_symbols: &PySymbolTable, + ast_symbols: &SymbolTable, + qargs: T, +) -> PyResult> +where + T: IntoIterator + 'a, +{ + let items = qargs + .into_iter() + .map(|item| -> PyResult { + match item.expression() { + asg::Expr::GateOperand(operand) => match operand { + asg::GateOperand::Identifier(iden) => broadcast_bits_for_identifier( + py, + our_symbols, + iden.symbol().as_ref().unwrap(), + ), + asg::GateOperand::IndexedIdentifier(indexed) => { + let iden_symbol = indexed.identifier().as_ref().unwrap(); + indexed.indexes().iter().fold( + broadcast_bits_for_identifier(py, our_symbols, iden_symbol), + |item, index| { + item.and_then(|item| { + broadcast_apply_index(py, ast_symbols, item, index) + }) + }, + ) + } + asg::GateOperand::HardwareQubit(_) => { + Err(QASM3ImporterError::new_err("cannot handle hardware qubits")) + } + }, + ty => Err(QASM3ImporterError::new_err(format!( + "unhandled gate operand expression type: {:?}", + ty + ))), + } + }) + .collect::>>()?; + + let mut broadcast_len = None; + for item in items.iter() { + match (item, broadcast_len) { + (BroadcastItem::Bit(_), _) => (), + (BroadcastItem::Register(reg), Some(len)) => { + if reg.len() != len { + return Err(QASM3ImporterError::new_err("invalid broadcast")); + } + } + (BroadcastItem::Register(reg), None) => { + broadcast_len = Some(reg.len()); + } + } + } + Ok(BroadcastQubitsIter { + py, + len: broadcast_len.unwrap_or(if items.is_empty() { 0 } else { 1 }), + offset: 0, + items, + }) +} diff --git a/crates/qasm3/src/lib.rs b/crates/qasm3/src/lib.rs new file mode 100644 index 000000000000..8efb5ac41169 --- /dev/null +++ b/crates/qasm3/src/lib.rs @@ -0,0 +1,129 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +mod build; +mod circuit; +mod error; +mod expr; + +use hashbrown::HashMap; + +use pyo3::prelude::*; +use pyo3::types::{PyModule, PyTuple}; + +use oq3_semantics::syntax_to_semantics::parse_source_string; + +use crate::error::QASM3ImporterError; + +/// Load an OpenQASM 3 program from a string into a :class:`.QuantumCircuit`. +#[pyfunction] +#[pyo3(pass_module)] +pub fn loads( + module: &PyModule, + py: Python, + source: String, + custom_gates: Option>, +) -> PyResult { + let result = parse_source_string(source, None); + if result.any_errors() { + result.print_errors(); + return Err(QASM3ImporterError::new_err( + "errors during parsing; see printed errors", + )); + } + let gates = match custom_gates { + Some(gates) => gates + .into_iter() + .map(|gate| (gate.name().to_owned(), gate)) + .collect(), + None => module + .getattr("_STDGATES_INC_GATES")? + .iter()? + .map(|obj| { + let gate = obj?.extract::()?; + Ok((gate.name().to_owned(), gate)) + }) + .collect::>>()?, + }; + crate::build::convert_asg(py, result.program(), result.symbol_table(), gates) +} + +fn stdgates_inc_gates(py: Python) -> PyResult<&PyTuple> { + let library = PyModule::import(py, "qiskit.circuit.library")?; + Ok(PyTuple::new( + py, + vec![ + circuit::PyGate::new(py, library.getattr("PhaseGate")?, "p".to_owned(), 1, 1) + .into_py(py), + circuit::PyGate::new(py, library.getattr("XGate")?, "x".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("YGate")?, "y".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("ZGate")?, "z".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("HGate")?, "h".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("SGate")?, "s".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("SdgGate")?, "sdg".to_owned(), 0, 1) + .into_py(py), + circuit::PyGate::new(py, library.getattr("TGate")?, "t".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("TdgGate")?, "tdg".to_owned(), 0, 1) + .into_py(py), + circuit::PyGate::new(py, library.getattr("SXGate")?, "sx".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("RXGate")?, "rx".to_owned(), 1, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("RYGate")?, "ry".to_owned(), 1, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("RZGate")?, "rz".to_owned(), 1, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("CXGate")?, "cx".to_owned(), 0, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("CYGate")?, "cy".to_owned(), 0, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("CZGate")?, "cz".to_owned(), 0, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("CPhaseGate")?, "cp".to_owned(), 1, 2) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CRXGate")?, "crx".to_owned(), 1, 2) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CRYGate")?, "cry".to_owned(), 1, 2) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CRZGate")?, "crz".to_owned(), 1, 2) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CHGate")?, "ch".to_owned(), 0, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("SwapGate")?, "swap".to_owned(), 0, 2) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CCXGate")?, "ccx".to_owned(), 0, 3) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CSwapGate")?, "cswap".to_owned(), 0, 3) + .into_py(py), + circuit::PyGate::new(py, library.getattr("CUGate")?, "cu".to_owned(), 4, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("CXGate")?, "CX".to_owned(), 0, 2).into_py(py), + circuit::PyGate::new(py, library.getattr("PhaseGate")?, "phase".to_owned(), 1, 1) + .into_py(py), + circuit::PyGate::new( + py, + library.getattr("CPhaseGate")?, + "cphase".to_owned(), + 1, + 2, + ) + .into_py(py), + circuit::PyGate::new(py, library.getattr("IGate")?, "id".to_owned(), 0, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("U1Gate")?, "u1".to_owned(), 1, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("U2Gate")?, "u2".to_owned(), 2, 1).into_py(py), + circuit::PyGate::new(py, library.getattr("U3Gate")?, "u3".to_owned(), 3, 1).into_py(py), + ], + )) +} + +#[pymodule] +fn _qasm3(py: Python<'_>, module: &PyModule) -> PyResult<()> { + module.add_function(wrap_pyfunction!(loads, module)?)?; + module.add_class::()?; + module.add( + "QASM3ImporterError", + py.get_type::(), + )?; + module.add("_STDGATES_INC_GATES", stdgates_inc_gates(py)?)?; + Ok(()) +} diff --git a/qiskit/qasm/libs/dummy/stdgates.inc b/qiskit/qasm/libs/dummy/stdgates.inc new file mode 100644 index 000000000000..d9deaf261606 --- /dev/null +++ b/qiskit/qasm/libs/dummy/stdgates.inc @@ -0,0 +1,75 @@ +// OpenQASM 3 standard gate library + +// phase gate +gate p(lambda) a {} + +// Pauli gate: bit-flip or NOT gate +gate x a {} +// Pauli gate: bit and phase flip +gate y a {} +// Pauli gate: phase flip +gate z a {} + +// Clifford gate: Hadamard +gate h a {} +// Clifford gate: sqrt(Z) or S gate +gate s a {} +// Clifford gate: inverse of sqrt(Z) +gate sdg a {} + +// sqrt(S) or T gate +gate t a {} +// inverse of sqrt(S) +gate tdg a {} + +// sqrt(NOT) gate +gate sx a {} + +// Rotation around X-axis +gate rx(theta) a {} +// rotation around Y-axis +gate ry(theta) a {} +// rotation around Z axis +gate rz(lambda) a {} + +// controlled-NOT +gate cx c, t {} +// controlled-Y +gate cy a, b {} +// controlled-Z +gate cz a, b {} +// controlled-phase +gate cp(lambda) a, b {} +// controlled-rx +gate crx(theta) a, b {} +// controlled-ry +gate cry(theta) a, b {} +// controlled-rz +gate crz(theta) a, b {} +// controlled-H +gate ch a, b {} + +// swap +gate swap a, b {} + +// Toffoli +gate ccx a, b, c {} +// controlled-swap +gate cswap a, b, c {} + +// four parameter controlled-U gate with relative phase +gate cu(theta, phi, lambda, gamma) c, t {} + +// Gates for OpenQASM 2 backwards compatibility +// CNOT +gate CX c, t {} +// phase gate +gate phase(lambda) q {} +// controlled-phase +gate cphase(lambda) a, b {} +// identity or idle gate +gate id a {} +// IBM Quantum experience gates +gate u1(lambda) q {} +gate u2(phi, lambda) q {} +gate u3(theta, phi, lambda) q {} diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index 44e46f9bea80..c1398aff747d 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -435,7 +435,7 @@ def _lookup_variable(self, variable) -> ast.Identifier: def build_header(self): """Builds a Header""" - version = ast.Version("3") + version = ast.Version("3.0") includes = self.build_includes() return ast.Header(version, includes) diff --git a/setup.py b/setup.py index 91fb20c83cf2..4bf16f2fc9c7 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,12 @@ binding=Binding.PyO3, debug=rust_debug, ), + RustExtension( + "qiskit._qasm3", + "crates/qasm3/Cargo.toml", + binding=Binding.PyO3, + debug=rust_debug, + ), ], options={"bdist_wheel": {"py_limited_api": "cp38"}}, )