From 5e87dd94f216a87f4d27dee44d178578d40e7ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= <121866228+aborgna-q@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:09:12 +0000 Subject: [PATCH] feat!: Track circuit extensions and read/write packages (#680) Removes the guppy-specific and adds supports for loading functions packages and standalone hugrs. Temporarily keeps track of the required extensions for the hugr in an optional `Circuit::required_extensions` field until https://github.com/CQCL/hugr/issues/1613 gets implemented. Fallbacks to a default set when loading bare hugrs. Note that storing a circuit with a non-root parent is currently an error. We'll need to store some pointer to the entrypoint on the hugr's metadata, and that'll require some serialization-stable path encoding. I'll open an issue for that . blocked-by: https://github.com/CQCL/hugr/pull/1621. I'll remove the patch in cargo.toml once that gets released. drive-by: Use `circuit_hash` for the `PartialEq` implementation of circuits. The derived equality failed on graphs with different node indices. BREAKING CHANGE: Removed `load_guppy_*` methods. Use `Circuit::load_function_reader` instead. --- Cargo.lock | 442 ++++++++++++++++++++------- Cargo.toml | 13 +- tket2-py/examples/utils/__init__.py | 2 +- tket2-py/src/circuit/tk2circuit.rs | 59 ++-- tket2-py/tket2/_tket2/circuit.pyi | 25 +- tket2-py/tket2/circuit/build.py | 41 ++- tket2/src/circuit.rs | 80 ++++- tket2/src/passes/pytket.rs | 3 +- tket2/src/serialize.rs | 459 +++++++++++++++++++++++++++- tket2/src/serialize/guppy.rs | 162 ---------- 10 files changed, 975 insertions(+), 311 deletions(-) delete mode 100644 tket2/src/serialize/guppy.rs diff --git a/Cargo.lock b/Cargo.lock index 41375d1f..63d375bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,9 +34,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -49,36 +49,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -197,9 +197,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.31" +version = "1.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70" dependencies = [ "jobserver", "libc", @@ -317,7 +317,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -343,9 +343,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -539,7 +539,7 @@ checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -550,7 +550,7 @@ checksum = "bc2323e10c92e1cf4d86e11538512e6dc03ceb586842970b6332af3d4046a046" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -572,7 +572,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -592,7 +592,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", "unicode-xid", ] @@ -606,6 +606,17 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -627,7 +638,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -739,7 +750,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -815,9 +826,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" @@ -857,9 +868,9 @@ dependencies = [ [[package]] name = "hugr" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d3b355ab819143e7dd85bda162f1a496fbbf8485a4bb1ff55f5dd52b031fca" +checksum = "51c5f880029c2ec70b4884f69cd5fd55c628e58830e9225a82bcea7f776ac963" dependencies = [ "hugr-core", "hugr-passes", @@ -867,9 +878,9 @@ dependencies = [ [[package]] name = "hugr-cli" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3407f8549502a9b8905db5b1ec5dd70bfc05b072313a3f2381ec3414f450f80d" +checksum = "617a0bf998c9f809eb634a8381896a60a4d7ae418d3db2eb1c661a083c3dd16f" dependencies = [ "clap", "clap-verbosity-flag", @@ -883,9 +894,9 @@ dependencies = [ [[package]] name = "hugr-core" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86a0796418a62b4cd83c3776021ec46671142fd46b5d355e2078db4c8d286c3" +checksum = "5d6428f0512a92cc3495e70c709383464bf1deef7ac566f812575e28dad52273" dependencies = [ "bitvec", "bumpalo", @@ -917,9 +928,9 @@ dependencies = [ [[package]] name = "hugr-passes" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a375f48c93f2839faad0a2ab6a58c4c88ad656d70398dbd6b45e6371362348" +checksum = "f4ae6bd670bc95f716d153049574e6420fed970edde63ea228529a8df0df2838" dependencies = [ "hugr-core", "itertools 0.13.0", @@ -952,14 +963,143 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1089,6 +1229,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "log" version = "0.4.22" @@ -1292,7 +1438,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -1318,9 +1464,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1442,18 +1588,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" dependencies = [ "cfg-if", "indoc", @@ -1469,9 +1615,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", "target-lexicon", @@ -1479,9 +1625,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" dependencies = [ "libc", "pyo3-build-config", @@ -1489,27 +1635,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] name = "pyo3-macros-backend" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -1559,9 +1705,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1640,7 +1786,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.82", + "syn 2.0.87", "unicode-ident", ] @@ -1661,9 +1807,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" dependencies = [ "bitflags", "errno", @@ -1719,7 +1865,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -1788,6 +1934,12 @@ dependencies = [ "serde", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -1813,7 +1965,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -1829,15 +1981,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "tap" version = "1.0.1" @@ -1865,22 +2028,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -1945,30 +2108,25 @@ dependencies = [ ] [[package]] -name = "tinytemplate" -version = "1.2.1" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "serde", - "serde_json", + "displaydoc", + "zerovec", ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "tinyvec_macros", + "serde", + "serde_json", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tket-json-rs" version = "0.6.2" @@ -2116,7 +2274,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -2187,7 +2345,7 @@ checksum = "70b20a22c42c8f1cd23ce5e34f165d4d37038f5b663ad20fb6adbdf029172483" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", ] [[package]] @@ -2196,27 +2354,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-xid" version = "0.2.6" @@ -2231,9 +2374,9 @@ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -2246,12 +2389,24 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2311,7 +2466,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -2333,7 +2488,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2584,6 +2739,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -2593,6 +2760,73 @@ dependencies = [ "tap", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index c1f76830..5d011e98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,19 @@ license = "Apache-2.0" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci_run)'] } missing_docs = "warn" +[patch.crates-io] + +# Uncomment to use unreleased versions of hugr +#hugr-core = { git = "https://github.com/CQCL/hugr.git" } +#hugr = { git = "https://github.com/CQCL/hugr.git" } +#hugr-cli = { git = "https://github.com/CQCL/hugr.git" } + [workspace.dependencies] # Make sure to run `just recompile-eccs` if the hugr serialisation format changes. -hugr = "0.13.2" -hugr-core = "0.13.2" -hugr-cli = "0.13.2" +hugr = "0.13.3" +hugr-core = "0.13.3" +hugr-cli = "0.13.3" portgraph = "0.12" pyo3 = "0.22.5" itertools = "0.13.0" diff --git a/tket2-py/examples/utils/__init__.py b/tket2-py/examples/utils/__init__.py index 0fd73dfc..4bab9670 100644 --- a/tket2-py/examples/utils/__init__.py +++ b/tket2-py/examples/utils/__init__.py @@ -33,6 +33,6 @@ def guppy_to_circuit(func_def: RawFunctionDef) -> Tk2Circuit: pkg = module.compile() json = pkg.to_json() - circ = Tk2Circuit.from_guppy_json(json, func_def.name) + circ = Tk2Circuit.from_package_json(json, func_def.name) return lower_to_pytket(circ) diff --git a/tket2-py/src/circuit/tk2circuit.rs b/tket2-py/src/circuit/tk2circuit.rs index 7677b336..9a1bb283 100644 --- a/tket2-py/src/circuit/tk2circuit.rs +++ b/tket2-py/src/circuit/tk2circuit.rs @@ -1,6 +1,7 @@ //! Rust-backed representation of circuits use std::borrow::{Borrow, Cow}; +use std::fmt::Display; use std::mem; use hugr::builder::{CircuitBuilder, DFGBuilder, Dataflow, DataflowHugr}; @@ -91,32 +92,54 @@ impl Tk2Circuit { // // TODO: Bind a messagepack encoder/decoder too. pub fn to_hugr_json(&self) -> PyResult { - Ok(serde_json::to_string(self.circ.hugr()).unwrap()) + fn err(e: impl Display) -> PyErr { + PyErr::new::(format!("Could not encode circuit: {e}")) + }; + let mut buf = Vec::new(); + self.circ.to_hugr_writer(&mut buf).map_err(err)?; + let res = std::str::from_utf8(&buf).map_err(err)?; + Ok(res.to_string()) } - /// Decode a HUGR json string to a circuit. + /// Encode the circuit as a Hugr Package json string. + // + // TODO: Bind a messagepack encoder/decoder too. + pub fn to_package_json(&self) -> PyResult { + fn err(e: impl Display) -> PyErr { + PyErr::new::(format!("Could not encode circuit: {e}")) + }; + let mut buf = Vec::new(); + self.circ.to_package_writer(&mut buf).map_err(err)?; + let res = std::str::from_utf8(&buf).map_err(err)?; + Ok(res.to_string()) + } + + /// Decode a HUGR json to a circuit. #[staticmethod] pub fn from_hugr_json(json: &str) -> PyResult { - let mut pkg: Package = serde_json::from_str(json) - .map_err(|e| PyErr::new::(format!("Invalid encoded HUGR: {e}")))?; - let mut reg = REGISTRY.clone(); - pkg.update_validate(&mut reg).map_err(|e| { - PyErr::new::(format!("Invalid encoded circuit: {e}")) - })?; - let Ok(hugr) = pkg.modules.into_iter().exactly_one() else { - return Err(PyValueError::new_err( - "Invalid HUGR json: Package must contain exactly one hugr.", - )); + fn err(e: impl Display) -> PyErr { + PyErr::new::(format!("Could not read hugr: {e}")) }; - Ok(Tk2Circuit { circ: hugr.into() }) + let circ = Circuit::load_hugr_reader(json.as_bytes()).map_err(err)?; + Ok(Tk2Circuit { circ }) } - /// Load a function from a compiled guppy module, encoded as a json string. + /// Decode a HUGR Package json to a circuit. + /// + /// Traverses the package's modules in order until it finds one containing a + /// function named `function_name`, and loads it as a circuit. + /// + /// If the json is a hugr json, it will be decoded as a `main` function in an empty module. + /// + /// When `function_name` is not given, it defaults to `main`. #[staticmethod] - pub fn from_guppy_json(json: &str, function: &str) -> PyResult { - let circ = tket2::serialize::load_guppy_json_str(json, function).map_err(|e| { - PyErr::new::(format!("Invalid encoded circuit: {e}")) - })?; + #[pyo3(signature = (json, function_name = None))] + pub fn from_package_json(json: &str, function_name: Option) -> PyResult { + fn err(e: impl Display) -> PyErr { + PyErr::new::(format!("Could not read package: {e}")) + }; + let name = function_name.unwrap_or_else(|| "main".to_string()); + let circ = Circuit::load_function_reader(json.as_bytes(), &name).map_err(err)?; Ok(Tk2Circuit { circ }) } diff --git a/tket2-py/tket2/_tket2/circuit.pyi b/tket2-py/tket2/_tket2/circuit.pyi index af690162..dcb3633a 100644 --- a/tket2-py/tket2/_tket2/circuit.pyi +++ b/tket2-py/tket2/_tket2/circuit.pyi @@ -54,18 +54,31 @@ class Tk2Circuit: """The output node of the circuit.""" def to_hugr_json(self) -> str: - """Encode the circuit as a HUGR json string.""" + """Encode the circuit as a HUGR json.""" + + def to_package_json(self) -> str: + """Encode the circuit as a HUGR Package json.""" @staticmethod def from_hugr_json(json: str) -> Tk2Circuit: """Decode a HUGR json string to a Tk2Circuit.""" - def to_tket1_json(self) -> str: - """Encode the circuit as a pytket json string.""" - @staticmethod - def from_guppy_json(json: str, function: str) -> Tk2Circuit: - """Load a function from a compiled guppy module, encoded as a json string.""" + def from_package_json(json: str, function_name: str | None = None) -> Tk2Circuit: + """Decode a HUGR Package json to a circuit. + + Traverses the package's modules in order until it finds one containing a + function named `function_name`, and loads it as a circuit. + + If the json is a hugr json, it will be decoded as a `main` function in an empty module. + + When `function_name` is not given, it defaults to `main`. + """ + + def to_tket1_json( + self, + ) -> str: + """Encode the circuit as a pytket json string.""" @staticmethod def from_tket1_json(json: str) -> Tk2Circuit: diff --git a/tket2-py/tket2/circuit/build.py b/tket2-py/tket2/circuit/build.py index 0d9a540e..c0b5554f 100644 --- a/tket2-py/tket2/circuit/build.py +++ b/tket2-py/tket2/circuit/build.py @@ -1,11 +1,12 @@ from __future__ import annotations from typing import Iterable -from hugr import tys, ops +from hugr import Hugr, tys, ops from hugr.package import Package from hugr.ext import Extension from hugr.ops import ComWire, Command from hugr.std.float import FLOAT_T +from hugr.build.function import Module from hugr.build.tracked_dfg import TrackedDfg from tket2.circuit import Tk2Circuit @@ -20,17 +21,32 @@ class CircBuild(TrackedDfg): def with_nqb(cls, n_qb: int) -> CircBuild: return cls(*[tys.Qubit] * n_qb, track_inputs=True) + def finish_hugr(self) -> Hugr: + """Finish building the hugr by setting all the qubits as the output. + + Returns: + The finished Hugr. + """ + return self.hugr + def finish_package( - self, other_extensions: Iterable[Extension] | None = None + self, + *, + other_extensions: Iterable[Extension] | None = None, + function_name="main", ) -> Package: """Finish building the package by setting all the qubits as the output and wrap it in a hugr package with the required extensions. Args: other_extensions: Other extensions to include in the package. + function_name: The name of the function containing the circuit in + the package's module. Defaults to "main". Returns: The finished package. """ + # TODO: Replace with `finish_hugr` once extensions are included in the hugr itself. + # See https://github.com/CQCL/hugr/pull/1621 import tket2.extensions as ext extensions = [ @@ -42,13 +58,26 @@ def finish_package( *(other_extensions or []), ] - return Package(modules=[self.hugr], extensions=extensions) + # Convert the DFG into a Function definition + dfg_op = self.hugr[self.hugr.root].op + assert type(dfg_op) is ops.DFG, "CircBuild must have a Dfg root" + self.hugr[self.hugr.root].op = ops.FuncDefn( + function_name, inputs=dfg_op.inputs, _outputs=dfg_op.outputs + ) + + # Insert it into a module, as required by the package. + module = Module() + module.hugr.insert_hugr(self.hugr) + + return Package(modules=[module.hugr], extensions=extensions) def finish(self, other_extensions: list[Extension] | None = None) -> Tk2Circuit: """Finish building the circuit by setting all the qubits as the output and validate.""" - return load_hugr_pkg(self.finish_package(other_extensions)) + return Tk2Circuit.from_package_json( + self.finish_package(other_extensions=other_extensions).to_json() + ) def from_coms(*args: Command) -> Tk2Circuit: @@ -68,10 +97,6 @@ def from_coms(*args: Command) -> Tk2Circuit: return build.finish() -def load_hugr_pkg(package: Package) -> Tk2Circuit: - return Tk2Circuit.from_hugr_json(package.to_json()) - - def load_custom(serialized: bytes) -> ops.Custom: import hugr._serialization.ops as sops import json diff --git a/tket2/src/circuit.rs b/tket2/src/circuit.rs index cf428b7a..9886c423 100644 --- a/tket2/src/circuit.rs +++ b/tket2/src/circuit.rs @@ -8,6 +8,8 @@ pub mod units; use std::collections::HashSet; use std::iter::Sum; +use std::mem; +use std::sync::Arc; pub use command::{Command, CommandIterator}; pub use hash::CircuitHash; @@ -20,7 +22,7 @@ use hugr::hugr::hugrmut::HugrMut; use hugr::ops::dataflow::IOTrait; use hugr::ops::{Input, NamedOp, OpName, OpParent, OpTag, OpTrait, Output}; use hugr::types::{PolyFuncType, Signature}; -use hugr::{Hugr, PortIndex}; +use hugr::{Extension, Hugr, PortIndex}; use hugr::{HugrView, OutgoingPort}; use itertools::Itertools; use lazy_static::lazy_static; @@ -29,10 +31,12 @@ pub use hugr::ops::OpType; pub use hugr::types::{EdgeKind, Type, TypeRow}; pub use hugr::{Node, Port, Wire}; +use crate::extension; + use self::units::{filter, LinearUnit, Units}; /// A quantum circuit, represented as a function in a HUGR. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Circuit { /// The HUGR containing the circuit. hugr: T, @@ -40,13 +44,33 @@ pub struct Circuit { /// /// This is checked at runtime to ensure that the node is a DFG node. parent: Node, + /// An optional set of extensions required to validate the circuit, + /// not including the prelude. + /// + /// Wrapped in an Arc to allow sharing between circuits, specially for borrowed circuits. + /// + /// Defaults to an standard set of quantum extensions and Hugr's std set. + required_extensions: Option>>, } impl Default for Circuit { fn default() -> Self { let hugr = T::default(); let parent = hugr.root(); - Self { hugr, parent } + Self { + hugr, + parent, + required_extensions: None, + } + } +} + +impl PartialEq for Circuit { + fn eq(&self, other: &Self) -> bool { + match (self.circuit_hash(), other.circuit_hash()) { + (Ok(hash1), Ok(hash2)) => hash1 == hash2, + _ => false, + } } } @@ -64,11 +88,22 @@ lazy_static! { set.insert(format!("prelude.{}", LiftDef.name()).into()); set }; + + /// A default set of required extensions for a circuit, + /// used when loading with hugr with no pre-defined extension set. + /// + /// We should be able to drop this once hugrs embed their required extensions. + /// See https://github.com/CQCL/hugr/issues/1613 + static ref DEFAULT_REQUIRED_EXTENSIONS: Vec = extension::REGISTRY.iter().map(|(_, ext)| ext.clone()).collect(); } +/// The [IGNORED_EXTENSION_OPS] definition depends on the buggy behaviour of [`NamedOp::name`], which returns bare names instead of scoped names on some cases. +/// Once this test starts failing it should be time to drop the `format!("prelude.{}", ...)`. +/// https://github.com/CQCL/hugr/issues/1496 #[test] fn issue_1496_remains() { assert_eq!("Noop", NoopDef.name()) } + impl Circuit { /// Create a new circuit from a HUGR and a node. /// @@ -77,7 +112,11 @@ impl Circuit { /// Returns an error if the parent node is not a DFG node in the HUGR. pub fn try_new(hugr: T, parent: Node) -> Result { check_hugr(&hugr, parent)?; - Ok(Self { hugr, parent }) + Ok(Self { + hugr, + parent, + required_extensions: None, + }) } /// Create a new circuit from a HUGR and a node. @@ -114,12 +153,40 @@ impl Circuit { &mut self.hugr } + /// Get the required extensions for the circuit. + /// + /// If no extension set was defined, returns the default set of quantum extensions and Hugr's std set. + /// + /// Note: This API is not currently public. We expect hugrs to embed their required extensions in the future, + /// at which point this method will be removed. + /// See https://github.com/CQCL/hugr/issues/1613 + pub(crate) fn required_extensions(&self) -> &[Extension] { + self.required_extensions + .as_deref() + .unwrap_or_else(|| &DEFAULT_REQUIRED_EXTENSIONS) + } + + /// Set the required extension set for the circuit. + /// + /// Returns the previous set of required extensions, if any. + /// + /// Note: This API is not currently public. We expect hugrs to embed their required extensions in the future, + /// at which point this method will be removed. + /// See https://github.com/CQCL/hugr/issues/1613 + pub(crate) fn set_required_extensions( + &mut self, + extensions: Arc>, + ) -> Option>> { + mem::replace(&mut self.required_extensions, Some(extensions)) + } + /// Ensures the circuit contains an owned HUGR. pub fn to_owned(&self) -> Circuit { let hugr = self.hugr.base_hugr().clone(); Circuit { hugr, parent: self.parent, + required_extensions: self.required_extensions.clone(), } } @@ -732,9 +799,14 @@ mod tests { }) .unwrap(); + let orig_circ = circ.clone(); + assert_eq!(circ, orig_circ); + assert_eq!(circ.qubit_count(), 2); assert!(remove_empty_wire(&mut circ, 1).is_ok()); assert_eq!(circ.qubit_count(), 1); + assert_ne!(circ, orig_circ); + assert_eq!( remove_empty_wire(&mut circ, 0).unwrap_err(), CircuitMutError::DeleteNonEmptyWire { diff --git a/tket2/src/passes/pytket.rs b/tket2/src/passes/pytket.rs index 04cba9c0..bd012692 100644 --- a/tket2/src/passes/pytket.rs +++ b/tket2/src/passes/pytket.rs @@ -4,6 +4,7 @@ //! This is a best-effort attempt, and may not always succeed. use derive_more::{Display, Error, From}; +use hugr::hugr::views::ExtractHugr; use itertools::Itertools; use crate::serialize::pytket::OpConvertError; @@ -12,7 +13,7 @@ use crate::Circuit; use super::find_tuple_unpack_rewrites; /// Try to lower a circuit to a form that can be encoded as a pytket legacy circuit. -pub fn lower_to_pytket(circ: &Circuit) -> Result { +pub fn lower_to_pytket(circ: &Circuit) -> Result { let mut circ = circ .extract_dfg() .map_err(|_| PytketLoweringError::NonLocalOperations)?; diff --git a/tket2/src/serialize.rs b/tket2/src/serialize.rs index efaa4c29..46d13f22 100644 --- a/tket2/src/serialize.rs +++ b/tket2/src/serialize.rs @@ -1,13 +1,464 @@ //! Utilities for serializing circuits. //! //! See [`crate::serialize::pytket`] for serialization to and from the legacy pytket format. -pub mod guppy; pub mod pytket; -pub use guppy::{ - load_guppy_json_file, load_guppy_json_reader, load_guppy_json_str, CircuitLoadError, -}; +use std::io; +use std::sync::Arc; + +use hugr::extension::ExtensionRegistryError; +use hugr::hugr::ValidationError; pub use pytket::{ load_tk1_json_file, load_tk1_json_reader, load_tk1_json_str, save_tk1_json_file, save_tk1_json_str, save_tk1_json_writer, TKETDecode, }; + +use derive_more::{Display, Error, From}; +use hugr::ops::{NamedOp, OpTag, OpTrait, OpType}; +use hugr::package::{Package, PackageEncodingError, PackageError, PackageValidationError}; +use hugr::{Hugr, HugrView, Node}; + +use crate::extension::REGISTRY; +use crate::{Circuit, CircuitError}; + +/// An encoded path pointing to a node in the HUGR, +/// to be used as the [`Circuit`] root. +/// +/// This key should not be used in the in-memory structure, as any modifications to the HUGR may +/// invalidate the path. +/// +/// TODO: Implement the path pointer. Currently this entry is not used. +#[allow(unused)] +const METADATA_ENTRYPOINT: &str = "TKET2.entrypoint"; + +impl> Circuit { + /// Store the circuit as a HUGR in json format. + /// + /// # Errors + /// + /// - If the circuit's parent is not the root of the HUGR. This will be relaxed in the future. + /// + // TODO: Store the path pointer on `METADATA_ENTRYPOINT`. + // We may need mutable access to `T` to avoid cloning the HUGR to add the entry. + pub fn to_hugr_writer(&self, writer: impl io::Write) -> Result<(), CircuitStoreError> { + let hugr = self.hugr().as_ref(); + + if self.parent() != hugr.root() { + return Err(CircuitStoreError::NonRootCircuit { + parent: self.parent(), + }); + } + + Ok(serde_json::to_writer(writer, hugr)?) + } + + /// Store the circuit as a package in json format. + /// + /// If the circuit is not a function in a module-rooted HUGR, a new module + /// is created with a `main` function containing it. + /// + /// # Errors + /// + /// - If the circuit's parent is not the root of the HUGR or a function in + /// the root's module. + /// + // TODO: Store the path pointer on `METADATA_ENTRYPOINT` instead. + pub fn to_package_writer(&self, writer: impl io::Write) -> Result<(), CircuitStoreError> { + let hugr = self.hugr().as_ref(); + + // Check if we support storing the circuit as a package. + // + // This restriction may be relaxed once `METADATA_ENTRYPOINT` is implemented. + let circuit_is_at_root = self.parent() == hugr.root(); + let circuit_is_fn_at_module_root = OpTag::ModuleRoot + .is_superset(hugr.get_optype(hugr.root()).tag()) + && hugr.get_parent(self.parent()) == Some(hugr.root()); + if !circuit_is_at_root && !circuit_is_fn_at_module_root { + return Err(CircuitStoreError::NonRootCircuit { + parent: self.parent(), + }); + } + + let mut pkg = Package::from_hugr(hugr.clone())?; + pkg.extensions = self.required_extensions().to_owned(); + + Ok(pkg.to_json_writer(writer)?) + } +} + +impl Circuit { + /// Load a circuit from a package or hugr json. + /// + /// If the json encodes a package, one of the modules must contain a + /// function called `function_name`. + /// + /// Otherwise, the json must encode a module-rooted HUGR containing the + /// named function. + pub fn load_function_reader( + json: impl io::Read, + function_name: impl AsRef, + ) -> Result { + let mut pkg = Package::from_json_reader(json)?; + pkg.update_validate(&mut REGISTRY.clone())?; + let Package { + modules, + extensions, + } = pkg; + + let (_module_idx, mut circ) = find_function_in_modules(modules, function_name.as_ref())?; + if !extensions.is_empty() { + circ.set_required_extensions(Arc::new(extensions)); + } + Ok(circ) + } + + /// Load a circuit from a hugr json. + /// + /// The circuit points to the Hugr's root or, if the `TKET2.entrypoint` metadata is present, + /// the indicated node. + /// + /// # Errors + /// + /// - If the target circuit root is not a dataflow container. + pub fn load_hugr_reader(json: impl io::Read) -> Result { + let mut hugr: Hugr = serde_json::from_reader(json)?; + hugr.update_validate(®ISTRY.clone())?; + // TODO: Read the entrypoint from the metadata. + let root = hugr.root(); + Ok(Circuit::try_new(hugr, root)?) + } +} + +/// Error type for serialization operations on [`Circuit`]s. +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] +pub enum CircuitStoreError { + /// Could not encode the hugr json. + EncodingError(serde_json::Error), + /// Cannot load the circuit file. + #[display("Cannot write to the circuit file: {_0}")] + InvalidFile(io::Error), + /// The circuit could not be stored as a package. + PackageStore(PackageError), + /// The circuit's parent is not the root of the HUGR. + #[display("The circuit's parent {parent} is not the root of the HUGR.")] + NonRootCircuit { + /// The parent node. + parent: Node, + }, +} + +/// Error type for deserialization operations on [`Circuit`]s. +#[derive(Debug, Display, Error, From)] +#[non_exhaustive] +pub enum CircuitLoadError { + /// Cannot load the circuit file. + #[display("Cannot load the circuit file: {_0}")] + #[from] + InvalidFile(io::Error), + /// Invalid JSON + #[display("Invalid HUGR JSON. {_0}")] + #[from] + InvalidJson(serde_json::Error), + /// The root node is not a module operation. + #[display( + "Expected a HUGR with a module at the root, but found a {} instead.", + root_op.name() + )] + NonModuleRoot { + /// The root operation. + root_op: OpType, + }, + /// The function is not found in the module. + #[display( + "Function '{function}' not found in the loaded module. Available functions: [{}]", + available_functions.join(", ") + )] + FunctionNotFound { + /// The function name. + function: String, + /// The available functions. + available_functions: Vec, + }, + /// The function has an invalid control flow structure. + #[display("Function '{function}' has an invalid control flow structure. Currently only flat functions with no control flow primitives are supported.")] + InvalidControlFlow { + /// The function name. + function: String, + }, + /// Error loading the circuit. + #[display("Error loading the circuit: {_0}")] + #[from] + CircuitLoadError(CircuitError), + /// Error loading the circuit. + #[from] + PackageError(PackageEncodingError), + + /// Error validating the loaded circuit. + #[from] + ValidationError(ValidationError), + /// An error that can occur in defining an extension registry while loading the circuit. + #[from] + ExtensionError(ExtensionRegistryError), + /// The encoded HUGR package must have a single HUGR. + #[display("The encoded HUGR package must have a single HUGR, but it has {count} HUGRs.")] + InvalidNumHugrs { + /// The number of HUGRs encountered in the encoded HUGR package. + count: usize, + }, +} + +impl From for CircuitStoreError { + fn from(e: PackageEncodingError) -> Self { + match e { + PackageEncodingError::Package(e) => CircuitStoreError::PackageStore(e), + PackageEncodingError::JsonEncoding(e) => CircuitStoreError::EncodingError(e), + PackageEncodingError::IOError(e) => CircuitStoreError::InvalidFile(e), + _ => panic!("Unexpected package encoding error: {e}"), + } + } +} + +impl From for CircuitLoadError { + fn from(e: PackageValidationError) -> Self { + match e { + PackageValidationError::Validation(e) => CircuitLoadError::ValidationError(e), + PackageValidationError::Extension(e) => CircuitLoadError::ExtensionError(e), + _ => panic!("Unexpected package validation error: {e}"), + } + } +} + +/// Looks for the circuit entrypoint in a list of modules, and returns a new +/// circuit pointing to it. +/// +/// The modules are searched in order, and the first match is returned. +/// +/// # Errors +/// +/// - If any of the HUGR roots is not a module operation. +/// - If the function is not found in any module. +fn find_function_in_modules( + modules: impl IntoIterator, + function_name: &str, +) -> Result<(usize, Circuit), CircuitLoadError> { + let mut available_functions = Vec::new(); + for (i, hugr) in modules.into_iter().enumerate() { + match find_function(hugr, function_name) { + Ok(circ) => return Ok((i, circ)), + Err(CircuitLoadError::FunctionNotFound { + available_functions: fns, + .. + }) => { + available_functions.extend(fns); + continue; + } + Err(e) => return Err(e), + } + } + Err(CircuitLoadError::FunctionNotFound { + function: function_name.to_string(), + available_functions, + }) +} + +/// Looks for the circuit entrypoint in a HUGR, and returns a new circuit pointing to it. +/// +/// # Errors +/// +/// - If the root of the HUGR is not a module operation. +/// - If the function is not found in the module. +fn find_function(hugr: Hugr, function_name: &str) -> Result { + // Find the root module. + let module = hugr.root(); + if !OpTag::ModuleRoot.is_superset(hugr.get_optype(module).tag()) { + return Err(CircuitLoadError::NonModuleRoot { + root_op: hugr.get_optype(module).clone(), + }); + } + + // Find the function definition. + fn func_name(op: &OpType) -> &str { + match op { + OpType::FuncDefn(decl) => &decl.name, + _ => "", + } + } + + let Some(function) = hugr + .children(module) + .find(|&n| func_name(hugr.get_optype(n)) == function_name) + else { + let available_functions = hugr + .children(module) + .map(|n| func_name(hugr.get_optype(n)).to_string()) + .collect(); + return Err(CircuitLoadError::FunctionNotFound { + function: function_name.to_string(), + available_functions, + }); + }; + + let circ = Circuit::try_new(hugr, function)?; + Ok(circ) +} + +#[cfg(test)] +mod tests { + use crate::Tk2Op; + + use super::*; + use std::io::Cursor; + + use cool_asserts::assert_matches; + use hugr::builder::{ + Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, HugrBuilder, + ModuleBuilder, + }; + use hugr::extension::prelude::QB_T; + use hugr::ops::handle::NodeHandle; + use hugr::type_row; + use hugr::types::Signature; + use itertools::Itertools; + use rstest::{fixture, rstest}; + + /// A circuit based on a DFG-rooted HUGR. + #[fixture] + fn root_circ() -> Circuit { + let mut h = DFGBuilder::new(Signature::new(vec![], vec![QB_T])).unwrap(); + + let res = h.add_dataflow_op(Tk2Op::QAlloc, []).unwrap(); + let q = res.out_wire(0); + + h.finish_hugr_with_outputs([q], ®ISTRY).unwrap().into() + } + + #[fixture] + fn function_circ() -> Circuit { + let mut h = ModuleBuilder::new(); + + let mut f = h + .define_function("banana", Signature::new(vec![], vec![QB_T])) + .unwrap(); + let res = f.add_dataflow_op(Tk2Op::QAlloc, []).unwrap(); + let q = res.out_wire(0); + let func_node = f.finish_with_outputs([q]).unwrap().handle().node(); + + Circuit::new(h.finish_hugr(®ISTRY).unwrap(), func_node) + } + + /// A circuit located inside a function in a module. + #[fixture] + fn nested_circ() -> Circuit { + let mut h = ModuleBuilder::new(); + + let mut f = h + .define_function("banana", Signature::new(vec![], vec![QB_T])) + .unwrap(); + let dfg = { + let mut dfg = f + .dfg_builder(Signature::new(vec![], type_row![QB_T]), []) + .unwrap(); + let res = dfg.add_dataflow_op(Tk2Op::QAlloc, []).unwrap(); + let q = res.out_wire(0); + dfg.finish_with_outputs([q]).unwrap() + }; + f.finish_with_outputs(dfg.outputs()) + .unwrap() + .handle() + .node(); + + Circuit::new(h.finish_hugr(®ISTRY).unwrap(), dfg.node()) + } + + #[fixture] + fn multi_module_pkg() -> Package { + fn define(name: &str, h: &mut ModuleBuilder) -> Node { + let f = h + .define_function(name, Signature::new(vec![QB_T], vec![QB_T])) + .unwrap(); + let inputs = f.input_wires().collect_vec(); + f.finish_with_outputs(inputs).unwrap().handle().node() + } + + let mut mod1 = ModuleBuilder::new(); + define("apple", &mut mod1); + define("banana", &mut mod1); + let mod1 = mod1.finish_prelude_hugr().unwrap(); + + let mut mod2 = ModuleBuilder::new(); + define("foo", &mut mod2); + define("bar", &mut mod2); + define("banana", &mut mod2); + let mod2 = mod2.finish_prelude_hugr().unwrap(); + + Package::new([mod1, mod2], []).unwrap() + } + + /// Test roundtrips of a circuit with a root parent. + #[rstest] + fn root_circuit_store(root_circ: Circuit) { + let mut buf = Vec::new(); + root_circ.to_hugr_writer(&mut buf).unwrap(); + let circ2 = Circuit::load_hugr_reader(Cursor::new(buf)).unwrap(); + assert_eq!(root_circ, circ2); + + let mut buf = Vec::new(); + root_circ.to_package_writer(&mut buf).unwrap(); + let circ2 = Circuit::load_function_reader(Cursor::new(buf), "main").unwrap(); + let extracted_circ2 = circ2.extract_dfg().unwrap(); + + assert_eq!(root_circ, extracted_circ2); + } + + #[rstest] + fn func_circuit_store(function_circ: Circuit) { + let mut buf = Vec::new(); + function_circ.to_package_writer(&mut buf).unwrap(); + let circ2 = Circuit::load_function_reader(Cursor::new(buf), "banana").unwrap(); + + assert_eq!(function_circ, circ2); + } + + #[rstest] + fn serialize_package_errors(multi_module_pkg: Package) { + let pkg_json = multi_module_pkg.to_json().unwrap(); + + match Circuit::load_function_reader(Cursor::new(&pkg_json), "not_found") { + Err(CircuitLoadError::FunctionNotFound { + function, + available_functions, + }) => { + assert_eq!(function, "not_found"); + assert_eq!( + available_functions, + ["apple", "banana", "foo", "bar", "banana"] + ); + } + Err(e) => panic!("Expected FunctionNotFound error got {e}."), + Ok(_) => panic!("Expected an error."), + }; + + assert_matches!( + Circuit::load_function_reader(Cursor::new(&pkg_json), "banana"), + Ok(_) + ) + } + + #[rstest] + fn root_errors(function_circ: Circuit, nested_circ: Circuit) { + // Trying to store a non-root circuit as a hugr. + let mut buf = Vec::new(); + assert_matches!( + function_circ.to_hugr_writer(&mut buf), + Err(CircuitStoreError::NonRootCircuit { .. }) + ); + + // Trying to store a non-root (and non-function-in-a-module) circuit as a package. + let mut buf = Vec::new(); + assert_matches!( + nested_circ.to_package_writer(&mut buf), + Err(CircuitStoreError::NonRootCircuit { .. }) + ); + } +} diff --git a/tket2/src/serialize/guppy.rs b/tket2/src/serialize/guppy.rs deleted file mode 100644 index a85accee..00000000 --- a/tket2/src/serialize/guppy.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Load pre-compiled guppy functions. - -use std::path::Path; -use std::{fs, io}; - -use derive_more::{Display, Error, From}; -use hugr::ops::{NamedOp, OpTag, OpTrait, OpType}; -use hugr::package::{Package, PackageValidationError}; -use hugr::{Hugr, HugrView}; -use itertools::Itertools; - -use crate::extension::REGISTRY; -use crate::{Circuit, CircuitError}; - -/// Loads a pre-compiled guppy file. -pub fn load_guppy_json_file( - path: impl AsRef, - function: &str, -) -> Result { - let file = fs::File::open(path)?; - let reader = io::BufReader::new(file); - load_guppy_json_reader(reader, function) -} - -/// Loads a pre-compiled guppy file from a json string. -pub fn load_guppy_json_str(json: &str, function: &str) -> Result { - let reader = json.as_bytes(); - load_guppy_json_reader(reader, function) -} - -/// Loads a pre-compiled guppy file from a reader. -pub fn load_guppy_json_reader( - reader: impl io::Read, - function: &str, -) -> Result { - let mut pkg: Package = serde_json::from_reader(reader)?; - pkg.update_validate(&mut REGISTRY.clone())?; - let count = pkg.modules.len(); - let Ok(hugr) = pkg.modules.into_iter().exactly_one() else { - return Err(CircuitLoadError::InvalidNumHugrs { count }); - }; - find_function(hugr, function) -} - -/// Looks for the required function in a HUGR compiled from a guppy module. -/// -/// Guppy functions are compiled into a root module, with each function as a `FuncDecl` child. -/// Each `FuncDecl` contains a `CFG` operation that defines the function. -/// -/// Currently we only support functions where the CFG operation has a single `DataflowBlock` child, -/// which we use as the root of the circuit. We (currently) do not support control flow primitives. -/// -/// # Errors -/// -/// - If the root of the HUGR is not a module operation. -/// - If the function is not found in the module. -/// - If the function has control flow primitives. -pub fn find_function(hugr: Hugr, function_name: &str) -> Result { - // Find the root module. - let module = hugr.root(); - if !OpTag::ModuleRoot.is_superset(hugr.get_optype(module).tag()) { - return Err(CircuitLoadError::NonModuleRoot { - root_op: hugr.get_optype(module).clone(), - }); - } - - // Find the function declaration. - fn func_name(op: &OpType) -> &str { - match op { - OpType::FuncDefn(decl) => &decl.name, - _ => "", - } - } - - let Some(function) = hugr - .children(module) - .find(|&n| func_name(hugr.get_optype(n)) == function_name) - else { - let available_functions = hugr - .children(module) - .map(|n| func_name(hugr.get_optype(n)).to_string()) - .collect(); - return Err(CircuitLoadError::FunctionNotFound { - function: function_name.to_string(), - available_functions, - }); - }; - - // Find the CFG operation. - let invalid_cfg = CircuitLoadError::InvalidControlFlow { - function: function_name.to_string(), - }; - let Ok(cfg) = hugr.children(function).skip(2).exactly_one() else { - return Err(invalid_cfg); - }; - - // Find the single dataflow block to use as the root of the circuit. - // The cfg node should only have the dataflow block and an exit node as children. - let mut cfg_children = hugr.children(cfg); - let Some(dataflow) = cfg_children.next() else { - return Err(invalid_cfg); - }; - if cfg_children.nth(1).is_some() { - return Err(invalid_cfg); - } - - let circ = Circuit::try_new(hugr, dataflow)?; - Ok(circ) -} - -/// Error type for conversion between `Op` and `OpType`. -#[derive(Debug, Display, Error, From)] -#[non_exhaustive] -pub enum CircuitLoadError { - /// Cannot load the circuit file. - #[display("Cannot load the circuit file: {_0}")] - #[from] - InvalidFile(io::Error), - /// Invalid JSON - #[display("Invalid JSON. {_0}")] - #[from] - InvalidJson(serde_json::Error), - /// The root node is not a module operation. - #[display( - "Expected a HUGR with a module at the root, but found a {} instead.", - root_op.name() - )] - NonModuleRoot { - /// The root operation. - root_op: OpType, - }, - /// The function is not found in the module. - #[display( - "Function '{function}' not found in the loaded module. Available functions: [{}]", - available_functions.join(", ") - )] - FunctionNotFound { - /// The function name. - function: String, - /// The available functions. - available_functions: Vec, - }, - /// The function has an invalid control flow structure. - #[display("Function '{function}' has an invalid control flow structure. Currently only flat functions with no control flow primitives are supported.")] - InvalidControlFlow { - /// The function name. - function: String, - }, - /// Error loading the circuit. - #[display("Error loading the circuit: {_0}")] - #[from] - CircuitLoadError(CircuitError), - /// Error validating the loaded circuit. - #[from] - PackageError(PackageValidationError), - /// The encoded HUGR package must have a single HUGR. - #[display("The encoded HUGR package must have a single HUGR, but it has {count} HUGRs.")] - InvalidNumHugrs { - /// The number of HUGRs encountered in the encoded HUGR package. - count: usize, - }, -}