diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml new file mode 100644 index 0000000..8e711d3 --- /dev/null +++ b/.github/workflows/rust.yaml @@ -0,0 +1,26 @@ +name: "Tests" +on: + push: + pull_request: + +jobs: + test: + name: cargo test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: cargo test --all-features + + # Check formatting with rustfmt + formatting: + name: cargo fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Ensure rustfmt is installed and setup problem matcher + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt + - name: Rustfmt Check + uses: actions-rust-lang/rustfmt@v1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3fabaa2..574ed15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,27 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "anstream" version = "0.6.13" @@ -56,6 +77,12 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "argmin" version = "0.10.0" @@ -89,12 +116,67 @@ dependencies = [ "thiserror", ] +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -129,7 +211,7 @@ version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.55", @@ -141,6 +223,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "colorchoice" version = "1.0.0" @@ -153,6 +241,143 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29daf137addc15da6bab6eae2c4a11e274b1d270bf2759508e62f6145e863ef6" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de619867d5de4c644b7fd9904d6e3295269c93d8a71013df796ab338681222d4" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.14.5", + "log", + "regalloc2", + "rustc-hash", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f5cf277490037d8dae9513d35e0ee8134670ae4a964a5ed5b198d4249d7c10" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3e22ecad1123343a3c09ac6ecc532bb5c184b6fcb7888df0ea953727f79924" + +[[package]] +name = "cranelift-control" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ca3ec6d30bce84ccf59c81fead4d16381a3ef0ef75e8403bc1e7385980da09" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eabb8d36b0ca8906bec93c78ea516741cac2d7e6b266fa7b0ffddcc09004990" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b42630229e49a8cfcae90bdc43c8c4c08f7a7aa4618b67f79265cd2f996dd2" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d1e36361805dfe0b6cdfd5a5ffdb5d03fa796170c5717d2727cbe623b93a0" + +[[package]] +name = "cranelift-native" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75aea85a0d7e1800b14ce9d3f53adf8ad4d1ee8a9e23b0269bdc50285e93b9b3" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.108.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac491fd3473944781f0cf9528c90cc899d18ad438da21961a839a3a44d57dfb" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -178,6 +403,46 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -191,18 +456,112 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "fxprof-processed-profile" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" +dependencies = [ + "bitflags", + "debugid", + "fxhash", + "serde", + "serde_json", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -214,11 +573,40 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -226,6 +614,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "indexmap" version = "2.2.6" @@ -233,9 +627,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", + "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "indxvec" version = "1.9.0" @@ -267,54 +668,160 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "matrixmultiply" -version = "0.3.8" +name = "ittapi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" dependencies = [ - "autocfg", - "rawpointer", + "anyhow", + "ittapi-sys", + "log", ] [[package]] -name = "medians" -version = "3.0.10" +name = "ittapi-sys" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881ba1880776c8b9b1cb7a5b45e0bb341df0b3fc32ef7cfc40ae059916cb4510" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" dependencies = [ - "indxvec", + "cc", ] [[package]] -name = "ndarray" -version = "0.15.6" +name = "jobserver" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "rawpointer", + "libc", ] [[package]] -name = "num-complex" -version = "0.4.5" +name = "leb128" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" -dependencies = [ - "num-traits", -] +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] -name = "num-integer" +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "medians" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881ba1880776c8b9b1cb7a5b45e0bb341df0b3fc32ef7cfc40ae059916cb4510" +dependencies = [ + "indxvec", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" @@ -331,12 +838,76 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" +dependencies = [ + "crc32fast", + "hashbrown 0.14.5", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -352,6 +923,78 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "pyo3" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.55", +] + [[package]] name = "qlafoutea" version = "0.1.0" @@ -360,11 +1003,14 @@ dependencies = [ "argmin", "argmin-math", "clap", + "csv", "derive_more", "itertools", "medians", "ndarray", "num-traits", + "pyo3", + "pyo3-build-config", "rand", "rayon", "serde", @@ -372,6 +1018,7 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", + "wasmtime", ] [[package]] @@ -448,6 +1095,51 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -457,12 +1149,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.22" @@ -500,6 +1211,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -513,6 +1233,44 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + +[[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" @@ -541,6 +1299,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "thiserror" version = "1.0.58" @@ -561,12 +1325,70 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -579,12 +1401,379 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-encoder" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d996306fb3aeaee0d9157adbe2f670df0236caf19f6728b221e92d0f27b3fe17" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.209.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" +dependencies = [ + "ahash", + "bitflags", + "hashbrown 0.14.5", + "indexmap", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2d8a7b4dabb460208e6b4334d9db5766e84505038b2529e69c3d07ac619115" +dependencies = [ + "anyhow", + "wasmparser", +] + +[[package]] +name = "wasmtime" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92a1370c66a0022e6d92dcc277e2c84f5dece19569670b8ce7db8162560d8b6" +dependencies = [ + "addr2line", + "anyhow", + "async-trait", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "fxprof-processed-profile", + "gimli", + "hashbrown 0.14.5", + "indexmap", + "ittapi", + "libc", + "libm", + "log", + "mach2", + "memfd", + "memoffset", + "object", + "once_cell", + "paste", + "postcard", + "psm", + "rayon", + "rustix", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "sptr", + "target-lexicon", + "wasm-encoder 0.207.0", + "wasmparser", + "wasmtime-asm-macros", + "wasmtime-cache", + "wasmtime-component-macro", + "wasmtime-component-util", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-slab", + "wasmtime-versioned-export-macros", + "wasmtime-winch", + "wat", + "windows-sys", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dee8679c974a7f258c03d60d3c747c426ed219945b6d08cbc77fd2eab15b2d1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-cache" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00103ffaf7ee980f4e750fe272b6ada79d9901659892e457c7ca316b16df9ec" +dependencies = [ + "anyhow", + "base64", + "directories-next", + "log", + "postcard", + "rustix", + "serde", + "serde_derive", + "sha2", + "toml", + "windows-sys", + "zstd", +] + +[[package]] +name = "wasmtime-component-macro" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cae30035f1cf97dcc6657c979cf39f99ce6be93583675eddf4aeaa5548509c" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.55", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-component-util" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ae611f08cea620c67330925be28a96115bf01f8f393a6cbdf4856a86087134" + +[[package]] +name = "wasmtime-cranelift" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2909406a6007e28be964067167890bca4574bd48a9ff18f1fa9f4856d89ea40" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-environ" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e227f9ed2f5421473723d6c0352b5986e6e6044fde5410a274a394d726108f" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "serde", + "serde_derive", + "target-lexicon", + "wasm-encoder 0.207.0", + "wasmparser", + "wasmprinter", + "wasmtime-component-util", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42edb392586d07038c1638e854382db916b6ca7845a2e6a7f8dc49e08907acdd" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "rustix", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b26ef7914af0c0e3ca811bdc32f5f66fbba0fd21e1f8563350e8a7951e3598" +dependencies = [ + "object", + "once_cell", + "rustix", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe088f9b56bb353adaf837bf7e10f1c2e1676719dd5be4cac8e37f2ba1ee5bc" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys", +] + +[[package]] +name = "wasmtime-slab" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ff75cafffe47b04b036385ce3710f209153525b0ed19d57b0cf44a22d446460" + +[[package]] +name = "wasmtime-types" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f2fa462bfea3220711c84e2b549f147e4df89eeb49b8a2a3d89148f6cc4a8b1" +dependencies = [ + "cranelift-entity", + "serde", + "serde_derive", + "smallvec", + "wasmparser", +] + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cedc5bfef3db2a85522ee38564b47ef3b7fc7c92e94cacbce99808e63cdd47" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "wasmtime-winch" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b27054fed6be4f3800aba5766f7ef435d4220ce290788f021a08d4fa573108" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "object", + "target-lexicon", + "wasmparser", + "wasmtime-cranelift", + "wasmtime-environ", + "winch-codegen", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "21.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936a52ce69c28de2aa3b5fb4f2dbbb2966df304f04cccb7aca4ba56d915fda0" +dependencies = [ + "anyhow", + "heck 0.4.1", + "indexmap", + "wit-parser", +] + +[[package]] +name = "wast" +version = "209.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fffef2ff6147e4d12e972765fd75332c6a11c722571d4ab7a780d81ffc8f0a4" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.209.1", +] + +[[package]] +name = "wat" +version = "1.209.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42203ec0271d113f8eb1f77ebc624886530cecb35915a7f63a497131f16e4d24" +dependencies = [ + "wast", +] + +[[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 = "winch-codegen" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dc69899ccb2da7daa4df31426dcfd284b104d1a85e1dae35806df0c46187f87" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "wasmparser", + "wasmtime-cranelift", + "wasmtime-environ", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -650,3 +1839,78 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-parser" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c83dab33a9618d86cfe3563cc864deffd08c17efc5db31a3b7cd1edeffe6e1" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index d64d27f..9daf418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,14 @@ anyhow = "1.0.81" argmin = { version = "0.10.0", features = ["rayon", "serde"] } argmin-math = "0.4.0" clap = { version = "4.5.4", features = ["derive"] } +csv = "1.3.0" derive_more = "0.99.17" itertools = "0.12.1" medians = "3.0.10" ndarray = "0.15.6" num-traits = "0.2.18" +pyo3 = "0.21.2" +pyo3-build-config = { version = "0.21.2", features = ["resolve-config"] } rand = "0.8.5" rayon = "1.10.0" serde = { version = "1.0.197", features = ["serde_derive", "derive", "rc"] } @@ -22,3 +25,4 @@ serde_derive = "1.0.201" serde_json = "1.0.115" serde_yaml = "0.9.34" thiserror = "1.0.58" +wasmtime = "21.0.1" diff --git a/README.md b/README.md index fde1ded..467328c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Qlafoutea Qlafoutea \kla.fu.ti\ -- (clafoutis) a crustless pie prepared from pourable batter and black cherries -- (Quantum Language of Formulas, User-facing Translation and Extension Architecture) - a highly-experimental language designed to let developers and scientists write - quantum algorithms and test them on existing and future quantum hardware without - first having to study for a PhD in quantum mechanics. +- (clafoutis) a delicious crustless pie prepared from pourable batter and black cherries +- (this piece of software) an ongoing experiment on designing a quantum-based calculator + for developers and scientists who wish to take advantage of quantum algorithms without + having to study quantum mechanics. Qlafoutea was born from the observation that most (if not all) of the tools currently available to develop quantum algorithms assume: @@ -19,3 +18,59 @@ available to develop quantum algorithms assume: 3. That the user of these tools is interested in building algorithms from scratch rather than interested in the result or the performance. +## Building & running + +You'll need [a recent version of Rust](https://rustup.rs/). + +To build & run (and display the command-line options): + +```sh +$ cargo run -- --help +``` + +To run tests: + +```sh +$ python3.10 -m venv venv # You only need to do this the first time. +$ . venv/bin/activate +$ cargo test +``` + + +## What it does + +As of this writing, qlafoutea supports two features: + +1. compiling a source QUBO file; +2. running a compiled QUBO file using its quantum simulator. + +To compile + +```sh +$ cargo run -- backend path-to-your-source-file.yaml +``` + +This will produce a compiled file, currently in the same JSON format as used by [Pulser](https://pulser.readthedocs.io/). + +To run + +```sh +$ cargo run -- run path-to-your-compiled-file.json +``` + +This will output a CSV files indicating how often each bitstring has been encountered during the execution of the compiled QUBO file. + + +## Is that it? + +Yes, this is a very early stage project, for the time being, this is it. + + +# What is QUBO? Why QUBO? + +[QUBO](https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization), or Quadratic Unconstrained Binary Optimization, is one optimization problem that has two interesting properties: + +1. it is very easy to execute on analog quantum architectures; +2. many problems [can easily be compiled to QUBO](https://blog.xa0.de/post/List-of-QUBO-formulations/). + +We figure that this can be a good candidate for an intermediate language for compiling some problems for quantum architectures. diff --git a/doc/coding.md b/doc/coding.md new file mode 100644 index 0000000..3cff445 --- /dev/null +++ b/doc/coding.md @@ -0,0 +1,3 @@ +# Coding conventions + +For the time being, the only coding convention is: please run `cargo fmt` before submitting code. diff --git a/doc/frontends.md b/doc/frontends.md new file mode 100644 index 0000000..2524954 --- /dev/null +++ b/doc/frontends.md @@ -0,0 +1,5 @@ +# How to add a front-end + +1. Create a parser. For a prototype, a JSON or YAML syntax will do perfectly. +2. Convert the problem to QUBO form, as an instance of `qlafoutea::backend::qubo::Constraints`. +3. Compile the code. \ No newline at end of file diff --git a/src/device/c6.rs b/src/backend/device/c6.rs similarity index 90% rename from src/device/c6.rs rename to src/backend/device/c6.rs index cffbf2e..732c03b 100644 --- a/src/device/c6.rs +++ b/src/backend/device/c6.rs @@ -1,3 +1,5 @@ +use crate::types::units::{Inv, Microseconds, Mul, Pow6, Rad, Value}; + #[derive(Clone, Copy, Debug)] pub struct C6Coeff { /// A value in rad/µs x µm^6. @@ -83,7 +85,11 @@ impl C6Coeff { }) } - pub fn value_rad_per_us_times_us_64(&self) -> f64 { + pub fn value_rad_per_us_times_um_6(&self) -> f64 { self.value_rad_per_us_times_us_64 } + + pub fn value_rad_per_us_times(&self) -> Value>>> { + Value::new(self.value_rad_per_us_times_us_64) + } } diff --git a/src/backend/device/layout.rs b/src/backend/device/layout.rs new file mode 100644 index 0000000..cecb659 --- /dev/null +++ b/src/backend/device/layout.rs @@ -0,0 +1,39 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone)] +pub struct Layout { + pub coordinates: Arc<[[f64; 2]]>, +} + +impl Serialize for Layout { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let schema = Schema { + coordinates: self.coordinates.clone(), + slug: "TriangularLatticeLayout(61, 5.0µm)".into(), + }; + schema.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Layout { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + Ok(Self { + coordinates: schema.coordinates, + }) + } +} + +#[derive(Deserialize, Serialize)] +struct Schema { + coordinates: Arc<[[f64; 2]]>, + slug: String, +} diff --git a/src/device/mod.rs b/src/backend/device/mod.rs similarity index 73% rename from src/device/mod.rs rename to src/backend/device/mod.rs index c34b7a8..c993251 100644 --- a/src/device/mod.rs +++ b/src/backend/device/mod.rs @@ -2,23 +2,28 @@ pub mod c6; pub mod layout; -use serde::Serialize; + +use c6::C6Coeff; +use serde::{Deserialize, Serialize}; use layout::Layout; -use crate::pulser::device::{ChannelId, PhysicalChannel, RydbergBeam, RydbergEom}; +use crate::backend::pulser::device::{ChannelId, PhysicalChannel, RydbergBeam, RydbergEom}; pub struct Device { interaction_coeff: c6::C6Coeff, dimensions: u32, rydberg_level: u32, max_atom_num: u32, - max_radial_distance: u32, + + /// Max distance to the center of the board, in um. + max_radial_distance_um: u32, + max_sq_distance_to_center_um_sq: f64, min_atom_distance: f64, max_sequence_duration: u32, is_virtual: bool, max_layout_filling: f64, - name: &'static str, + name: String, channels: Vec, pre_calibrated_layouts: Vec, } @@ -26,6 +31,15 @@ impl Device { pub fn interaction_coeff(&self) -> c6::C6Coeff { self.interaction_coeff } + pub fn max_sq_distance_to_center(&self) -> f64 { + self.max_sq_distance_to_center_um_sq + } + pub fn min_atom_distance(&self) -> f64 { + self.min_atom_distance + } + pub fn rydberg_level(&self) -> u32 { + self.rydberg_level + } } impl Serialize for Device { @@ -34,16 +48,16 @@ impl Serialize for Device { S: serde::Serializer, { let schema = Schema { - version: "1", + version: "1".into(), dimensions: self.dimensions, max_atom_num: self.max_atom_num, rydberg_level: self.rydberg_level, - max_radial_distance: self.max_radial_distance, + max_radial_distance: self.max_radial_distance_um, min_atom_distance: self.min_atom_distance, max_sequence_duration: self.max_sequence_duration, is_virtual: self.is_virtual, max_layout_filling: self.max_layout_filling, - name: self.name, + name: self.name.clone(), channels: self.channels.clone(), pre_calibrated_layouts: self.pre_calibrated_layouts.clone(), interaction_coeff_xy: None, @@ -54,9 +68,33 @@ impl Serialize for Device { } } -#[derive(Serialize)] +impl<'de> Deserialize<'de> for Device { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + Ok(Self { + dimensions: schema.dimensions, + max_atom_num: schema.max_atom_num, + rydberg_level: schema.rydberg_level, + max_radial_distance_um: schema.max_radial_distance, + min_atom_distance: schema.min_atom_distance, + max_sequence_duration: schema.max_sequence_duration, + is_virtual: schema.is_virtual, + max_layout_filling: schema.max_layout_filling, + name: schema.name, + channels: schema.channels.clone(), + pre_calibrated_layouts: schema.pre_calibrated_layouts.clone(), + interaction_coeff: C6Coeff::new(schema.rydberg_level).unwrap(), + max_sq_distance_to_center_um_sq: u64::pow(schema.max_radial_distance as u64, 2) as f64, + }) + } +} + +#[derive(Deserialize, Serialize)] struct Schema { - version: &'static str, + version: String, dimensions: u32, rydberg_level: u32, max_atom_num: u32, @@ -65,7 +103,7 @@ struct Schema { max_sequence_duration: u32, is_virtual: bool, max_layout_filling: f64, - name: &'static str, + name: String, channels: Vec, pre_calibrated_layouts: Vec, interaction_coeff_xy: Option, @@ -170,17 +208,20 @@ impl Device { min_retarget_interval: (), }]; + let max_radial_distance_um = 35; + let max_sq_distance_to_center_um_sq = (max_radial_distance_um as f64).powi(2); Self { interaction_coeff, dimensions: 2, rydberg_level, max_atom_num: 25, - max_radial_distance: 35, + max_radial_distance_um, min_atom_distance: 5., max_sequence_duration: 4_000, + max_sq_distance_to_center_um_sq, is_virtual: false, max_layout_filling: 0.5, - name: "AnalogDevice", + name: "AnalogDevice".into(), channels, pre_calibrated_layouts, } diff --git a/src/backend/format/mod.rs b/src/backend/format/mod.rs new file mode 100644 index 0000000..59d6f87 --- /dev/null +++ b/src/backend/format/mod.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use super::pulser::sequence; + +#[derive(Deserialize, Serialize)] +pub struct Code { + pub problem: crate::frontend::Input, + pub sequence: String, +} +impl Code { + pub fn try_new( + problem: crate::frontend::Input, + sequence: sequence::Sequence, + ) -> Result { + let sequence = serde_json::to_string_pretty(&sequence)?; + Ok(Self { problem, sequence }) + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..128a8d2 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1,6 @@ +//! Compiler back-end. +pub mod device; +pub mod format; +pub mod pulser; +pub mod qaa; +pub mod qubo; diff --git a/src/pulser/device.rs b/src/backend/pulser/device.rs similarity index 100% rename from src/pulser/device.rs rename to src/backend/pulser/device.rs diff --git a/src/pulser/mod.rs b/src/backend/pulser/mod.rs similarity index 100% rename from src/pulser/mod.rs rename to src/backend/pulser/mod.rs diff --git a/src/pulser/pulse.rs b/src/backend/pulser/pulse.rs similarity index 58% rename from src/pulser/pulse.rs rename to src/backend/pulser/pulse.rs index 643eabb..07db93b 100644 --- a/src/pulser/pulse.rs +++ b/src/backend/pulser/pulse.rs @@ -1,8 +1,8 @@ use std::rc::Rc; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use crate::pulser::waveform::Waveform; +use crate::backend::pulser::waveform::Waveform; pub struct Pulse { channel: Rc, @@ -28,22 +28,36 @@ impl Serialize for Pulse { amplitude: self.amplitude.clone(), detuning: self.detuning.clone(), channel: self.channel.clone(), - op: "pulse", + op: "pulse".to_string(), phase: 0., post_phase_shift: 0., - protocol: "min-delay", + protocol: "min-delay".to_string(), }; schema.serialize(serializer) } } -#[derive(Serialize)] +impl<'de> Deserialize<'de> for Pulse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + Ok(Self { + amplitude: schema.amplitude, + detuning: schema.detuning, + channel: schema.channel, + }) + } +} + +#[derive(Serialize, Deserialize)] struct Schema { amplitude: Waveform, detuning: Waveform, channel: Rc, - op: &'static str, + op: String, phase: f64, post_phase_shift: f64, - protocol: &'static str, + protocol: String, } diff --git a/src/pulser/register.rs b/src/backend/pulser/register.rs similarity index 53% rename from src/pulser/register.rs rename to src/backend/pulser/register.rs index 847de67..a00e154 100644 --- a/src/pulser/register.rs +++ b/src/backend/pulser/register.rs @@ -2,7 +2,8 @@ use std::sync::Arc; -use serde::Serialize; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; use crate::types::units::{Coordinates, Micrometers}; @@ -30,8 +31,8 @@ impl Serialize for Register { .enumerate() .map(|(index, c)| AtomSchema { name: format!("{index}"), - x: c.x, - y: c.y, + x: c.x.into_inner(), + y: c.y.into_inner(), }) .collect(), ); @@ -39,10 +40,31 @@ impl Serialize for Register { } } -#[derive(Serialize)] +impl<'de> Deserialize<'de> for Register { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + let coordinates = schema + .0 + .into_iter() + .map(|atom| Coordinates { + x: crate::types::units::Value::new(atom.x), + y: crate::types::units::Value::new(atom.y), + }) + .collect_vec(); + let register = Register { + coordinates: coordinates.into(), + }; + Ok(register) + } +} + +#[derive(Deserialize, Serialize)] pub struct Schema(Vec); -#[derive(Serialize)] +#[derive(Deserialize, Serialize)] pub struct AtomSchema { x: f64, y: f64, diff --git a/src/backend/pulser/sequence.rs b/src/backend/pulser/sequence.rs new file mode 100644 index 0000000..e38db39 --- /dev/null +++ b/src/backend/pulser/sequence.rs @@ -0,0 +1,103 @@ +use std::{collections::HashMap, rc::Rc}; + +use serde::{Deserialize, Serialize}; + +use crate::backend::{ + device::Device, + pulser::{device::ChannelId, pulse::Pulse, register::Register}, +}; + +pub struct Sequence { + register: Rc, + device: Rc, + pulse: Rc, + channels: Vec>, +} + +impl Sequence { + pub fn new(device: Device, register: Register, pulse: Pulse, channels: &[Rc]) -> Self { + Self { + register: Rc::new(register), + device: Rc::new(device), + pulse: Rc::new(pulse), + channels: channels.to_vec(), + } + } + pub fn register(&self) -> &Register { + &self.register + } + pub fn device(&self) -> &Device { + &self.device + } + pub fn pulse(&self) -> &Pulse { + &self.pulse + } + pub fn channels(&self) -> &[Rc] { + &self.channels + } +} + +impl<'de> Deserialize<'de> for Sequence { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + match schema.operations.len() { + 0 => { + return Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Other("empty operations"), + &"exactly one operation", + )) + } + n if n > 1 => { + return Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Other(&format!("{} operations", n)), + &"exactly one operation", + )) + } + _ => {} + } + Ok(Self { + register: schema.register, + device: schema.device, + pulse: schema.operations[0].clone(), + channels: schema.channels.keys().map(|k| k.clone().into()).collect(), + }) + } +} + +impl Serialize for Sequence { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let schema = Schema { + version: "1".to_string(), + name: "qlafoutea compilation target".to_string(), + register: self.register.clone(), + device: self.device.clone(), + variables: HashMap::new(), + operations: vec![self.pulse.clone()], + measurement: None, + channels: self + .channels + .iter() + .map(|chan| (chan.to_string(), ChannelId(chan.to_string()))) + .collect(), + }; + schema.serialize(serializer) + } +} + +#[derive(Deserialize, Serialize)] +struct Schema { + version: String, + variables: HashMap, // always empty for the time being. + register: Rc, + device: Rc, + name: String, + operations: Vec>, + channels: HashMap, + measurement: Option<()>, // always None for the time being. +} diff --git a/src/pulser/waveform.rs b/src/backend/pulser/waveform.rs similarity index 71% rename from src/pulser/waveform.rs rename to src/backend/pulser/waveform.rs index c75f164..be54876 100644 --- a/src/pulser/waveform.rs +++ b/src/backend/pulser/waveform.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use serde::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone)] pub enum Waveform { @@ -38,7 +38,7 @@ impl Serialize for Waveform { } = *self; let duration = timestamps.last().cloned().unwrap(); let schema = Schema { - kind: "interpolated", + kind: "interpolated".to_string(), duration, times: timestamps.iter().map(|v| v / duration).collect(), values: values.clone(), @@ -47,9 +47,22 @@ impl Serialize for Waveform { } } -#[derive(Serialize)] +impl<'de> Deserialize<'de> for Waveform { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let schema = Schema::deserialize(deserializer)?; + Ok(Waveform::Interpolated { + values: schema.values, + timestamps: schema.times.iter().map(|v| v * schema.duration).collect(), + }) + } +} + +#[derive(Deserialize, Serialize)] struct Schema { - kind: &'static str, + kind: String, duration: f64, times: Rc<[f64]>, values: Rc<[f64]>, diff --git a/src/qaa/mod.rs b/src/backend/qaa/mod.rs similarity index 97% rename from src/qaa/mod.rs rename to src/backend/qaa/mod.rs index 7aca50f..9144109 100644 --- a/src/qaa/mod.rs +++ b/src/backend/qaa/mod.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{ +use crate::backend::{ device::Device, pulser::{pulse::Pulse, register::Register, sequence::Sequence, waveform::Waveform}, qubo::Constraints, diff --git a/src/backend/qubo/mod.rs b/src/backend/qubo/mod.rs new file mode 100644 index 0000000..8464705 --- /dev/null +++ b/src/backend/qubo/mod.rs @@ -0,0 +1,402 @@ +//! QUBO format as an intermediate language +//! +//! QUBO (Quadratic unconstrained binary optimization) is an optimization problem +//! that generally maps well to quantum registers. +//! +//! Given a set of weights Q (a matrix), solving the QUBO problem means finding +//! the best values x in {0, 1} ^ n to minimize the following formula: +//! +//! sum_{i, j}(Q[i, j] * x [i] * x[j]) + +use std::fmt::Display; + +use argmin::{ + core::{CostFunction, Executor}, + solver::neldermead::NelderMead, +}; +use itertools::Itertools; +use medians::Medianf64; +use rand::{Rng, SeedableRng}; +use rayon::prelude::*; +use serde::{Deserialize, Serialize, Serializer}; + +use crate::{ + backend::{device::Device, pulser::register::Register}, + types::{ + units::{self, Coordinates, Inv, Micrometers, Microseconds, Mul, Rad}, + Quality, + }, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("index out of bounds")] + IndexOutOfBounds { + x: usize, + y: usize, + num_nodes: usize, + }, + + #[error("layout error #?")] + Layout(argmin::core::Error), + + #[error("failed to layout")] + NoSolution, + + #[error("algorithm cannot layout off-diagonal negative values")] + NegativeValueOffDiagonal, + + #[error("algorithm cannot layout on-diagonal positive values")] + PositiveValueOnDiagonal, + + #[error("value is infinite or not a number")] + InfiniteValue, +} + +#[derive(Clone, Debug)] +pub struct Options { + pub seed: u64, + pub min_quality: Quality, + pub max_iters: u64, + pub overflow_protection_threshold: f64, + pub overflow_protection_factor: f64, +} +impl Default for Options { + fn default() -> Self { + Self { + seed: 0, + min_quality: Quality::new(0.2), + max_iters: 4_000, + overflow_protection_threshold: 0.9, + overflow_protection_factor: 1_000., + } + } +} + +/// A set of qubo constraints. +/// +/// For (de)serialization, please use `format::Format`. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct Constraints { + /// A symmetric matrix of weights of size `num_nodes`. + /// + /// In practice, the sub-diagonal part of the matrix is redundant. + /// We could save ~half of the memory by removing this part, but at + /// the expense of increasing computation costs. + /// + /// In this implementation, we don't make the effort. + data: Vec, + num_nodes: usize, +} +#[allow(clippy::len_without_is_empty)] +impl Constraints { + pub fn new(num_nodes: usize) -> Self { + let data = vec![0.; num_nodes * num_nodes]; + Self { data, num_nodes } + } + + pub fn from_const(data: [[f64; N]; N]) -> Self { + let data = data.iter().flat_map(|d| d.iter()).cloned().collect(); + Self { data, num_nodes: N } + } + + #[allow(dead_code)] + pub fn try_new(num_nodes: usize, data: Vec) -> Option { + if data.len() != num_nodes * num_nodes { + return None; + } + Some(Self { data, num_nodes }) + } + + /// Return the number of constraints. + pub fn num_constraints(&self) -> usize { + self.num_nodes * self.num_nodes / 2 + } + + /// Return the number of nodes. + pub fn num_nodes(&self) -> usize { + self.num_nodes + } + + pub fn check_compilable_subset(&self) -> Result<(), Error> { + for (index, x) in self.data.iter().enumerate() { + if !x.is_finite() { + return Err(Error::InfiniteValue); + } + let is_diagonal = index % self.num_nodes == index / self.num_nodes; + if is_diagonal { + if x.is_sign_positive() { + return Err(Error::PositiveValueOnDiagonal); + } + } else if x.is_sign_negative() { + return Err(Error::NegativeValueOffDiagonal); + } + } + Ok(()) + } + + /// Attempt to layout a set of constraints as a Register. + /// + /// In the current implementation, we run N concurrent instances of a Nelder-Mead optimizer, + /// with distinct start states, where N is determined from the number of cores on the computer. + /// + /// In case of success, returns: + /// - Register: the geometry; + /// - Quality: an abstract measure of quality, where 0 is really bad and 1 is optimal; + /// - seed: the seed with which we found a solution. + pub fn layout(&self, device: &Device, options: &Options) -> Option<(Register, Quality, u64)> { + self.check_compilable_subset().expect("invalid content"); + (0..u64::MAX).into_par_iter().find_map_any(|seed| { + let seed = seed.wrapping_add(options.seed); + let mut rng = rand::rngs::StdRng::seed_from_u64(seed); + + // Set initial search points. + // + // Our search space has 2 * num_node dimensions (we're looking for 2 coordinates per node). + // By definition, Nelder-Mead must take dimensions + 1 starting points. + // + // Since we wish to be reproducible, we initialize these points from `rng`, which can be + // seeded by the caller. + let mut params = Vec::with_capacity(self.num_nodes * 2 + 1); + for _ in 0..self.num_nodes { + let mut state = vec![0f64; self.num_nodes * 2]; + rng.fill(state.as_mut_slice()); + params.push(state); + } + let solver = NelderMead::new(params); + + let cost = Cost { + constraints: self, + device, + options: options.clone(), + }; + + let optimized = Executor::new(cost, solver) + .configure(|state| state.max_iters(options.max_iters).target_cost(1e-6)) + .run() + .expect("Error in the execution of register optimizer"); + let quality = 1. - optimized.state.best_cost.atan() / std::f64::consts::FRAC_PI_2; + let quality = Quality::new(quality); + let coordinates = match optimized.state.best_param { + None => return None, + Some(v) => { + assert!(v.len() % 2 == 0); + let mut iter = v.into_iter(); + let mut coordinates = Vec::with_capacity(self.num_nodes); + while let Some((x, y)) = iter.next_tuple() { + coordinates.push(Coordinates::::new(x, y)) + } + coordinates + } + }; + let register = Register { + coordinates: coordinates.into(), + }; + + if quality >= options.min_quality { + Some((register, quality, seed)) + } else { + eprintln!("...testing seed {seed} => insufficient quality {}", quality); + None + } + }) + } + + pub fn omega(&self) -> f64 { + self.data + .iter() + .cloned() + .filter(|f| *f > 0.) + .collect_vec() + .medf_unchecked() + } +} + +impl Constraints { + pub fn at(&self, x: usize, y: usize) -> Result { + let index = self.index(x, y)?; + Ok(self.data[index]) + } + + fn get_mut(&mut self, x: usize, y: usize) -> Result<&mut f64, Error> { + let index = self.index(x, y)?; + Ok(&mut self.data[index]) + } + + /// Change a value at given coordinates. + pub fn delta_at(&mut self, x: usize, y: usize, delta: f64) -> Result<(), Error> { + let ref_mut = self.get_mut(x, y)?; + *ref_mut += delta; + Ok(()) + } + + fn index(&self, x: usize, y: usize) -> Result { + let (x, y) = if x >= self.num_nodes || y >= self.num_nodes { + return Err(Error::IndexOutOfBounds { + x, + y, + num_nodes: self.num_nodes, + }); + } else if x <= y { + (x, y) + } else { + (y, x) + }; + Ok(self.num_nodes * y + x) + } +} +impl Display for Constraints { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buf = String::new(); + for y in 0..self.num_nodes { + let mut line = if y == 0 { + "[".to_string() + } else { + " [".to_string() + }; + for x in 0..self.num_nodes { + if x == 0 { + line.push_str(&format!("{}", self.at(x, y).unwrap())); + } else { + line.push_str(&format!(", {}", self.at(x, y).unwrap())); + } + } + buf.push_str(&format!( + "{}]{}", + line, + if y + 1 == self.num_nodes { "]" } else { ",\n" } + )); + } + f.collect_str(&buf) + } +} + +struct Cost<'a> { + constraints: &'a Constraints, + device: &'a Device, + options: Options, +} +impl<'a> Cost<'a> { + fn actual_interaction( + &self, + first: Coordinates, + second: Coordinates, + ) -> units::Value>> { + units::Value::new( + self.device + .interaction_coeff() + .value_rad_per_us_times_um_6() + / first.sqdist(&second).cube().into_inner(), + ) + } + fn expected_interaction( + &self, + x: usize, + y: usize, + ) -> units::Value>> { + units::Value::new(self.constraints.at(x, y).unwrap()) + } +} + +impl<'a> CostFunction for Cost<'a> { + type Param = Vec; + + type Output = f64; + + fn cost(&self, param: &Self::Param) -> Result { + debug_assert_eq!(param.len(), 2 * self.constraints.num_nodes); + use crate::types::units::*; + + // First component: distance to our objective. + let distance = { + let mut total: Value>>> = Value::new(0.); + for i in 0..self.constraints.num_nodes { + let first = Coordinates::::new(param[2 * i], param[2 * i + 1]); + for j in i + 1..self.constraints.num_nodes { + let second = Coordinates::::new(param[2 * j], param[2 * j + 1]); + let actual_interaction = self.actual_interaction(first, second); + let expected_interaction = self.expected_interaction(i, j); + let diff = (actual_interaction - expected_interaction).sq(); + if i == j { + total += diff + } else { + total += 2. * diff + } + } + } + total.sqrt() + }; + + // Second component: make sure that all the atoms fit on the device + // (aka "overflow protection") + let max_sq_distance_to_center = param + .iter() + .tuples() + .map(|(x, y)| x * x + y * y) + .reduce(f64::max) + .unwrap(); + let overflow_risk = max_sq_distance_to_center / self.device.max_sq_distance_to_center(); + let overflow_cost = if overflow_risk < self.options.overflow_protection_threshold { + 0. + } else { + self.options.overflow_protection_factor + * (overflow_risk - self.options.overflow_protection_threshold).exp() + }; + + Ok(overflow_cost + distance.into_inner()) + } +} + +#[test] +// Test that the cost is roughly the same to the one we compute in Python. +fn test_cost_function_vs_python() { + let evaluator = Cost { + device: &Device::analog(), + constraints: &Constraints::try_new( + 5, + vec![ + -10.0, + 19.7365809, + 19.7365809, + 5.42015853, + 5.42015853, + 19.7365809, + -10.0, + 20.67626392, + 0.17675796, + 0.85604541, + 19.7365809, + 20.67626392, + -10.0, + 0.85604541, + 0.17675796, + 5.42015853, + 0.17675796, + 0.85604541, + -10.0, + 0.32306662, + 5.42015853, + 0.85604541, + 0.17675796, + 0.32306662, + -10.0, + ], + ) + .unwrap(), + options: Options::default(), + }; + let param = vec![ + 0.5488135, 0.71518937, 0.60276338, 0.54488318, 0.4236548, 0.64589411, 0.43758721, 0.891773, + 0.96366276, 0.38344152, + ]; + let cost = evaluator.cost(¶m).unwrap(); + let python_cost = 149417981060.90808; // Achieved manually from running Python implementation. + let diff = f64::abs(cost - python_cost) / f64::max(cost, python_cost); + assert!( + diff <= 0.01, + "Expected < 1% difference between {} and {}, got {}", + cost, + python_cost, + diff + ); +} diff --git a/src/device/layout.rs b/src/device/layout.rs deleted file mode 100644 index f53d2e1..0000000 --- a/src/device/layout.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::sync::Arc; - -use serde::Serialize; - -#[derive(Clone)] -pub struct Layout { - pub coordinates: Arc<[[f64; 2]]>, -} - -impl Serialize for Layout { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let schema = Schema { - coordinates: self.coordinates.clone(), - slug: "TriangularLatticeLayout(61, 5.0µm)", - }; - schema.serialize(serializer) - } -} - -#[derive(Serialize)] -struct Schema { - coordinates: Arc<[[f64; 2]]>, - slug: &'static str, -} diff --git a/src/frontend/max3sat/mod.rs b/src/frontend/max3sat/mod.rs new file mode 100644 index 0000000..26ef303 --- /dev/null +++ b/src/frontend/max3sat/mod.rs @@ -0,0 +1,304 @@ +use std::{borrow::Cow, collections::HashMap, fmt::Display, ops::Not, sync::Arc}; + +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{backend::qubo::Constraints, runtime::run::Sample}; + +pub type Input = Disjunction; + +/// A problem formulated as a AND of OR of variables/negated variables. +/// +/// The solution space is the mapping between the variables used in the problem +/// and booleans. A solution to the problem is valid iff the disjunction evaluates +/// to `true`. +#[derive(Deserialize, Serialize)] +pub struct Disjunction { + /// A logical "and" operation between a number of conjunctions. + /// + /// It evaluates to `true` for a solution iff all the conjunctions evaluate to `true`. + pub and: Vec, +} +impl Disjunction { + pub fn eval(&self, env: &Env) -> bool { + self.and.iter().all(|literal| literal.eval(env)) + } +} + +/// A logical conjunction, e.g. a OR of variables/negated variables. +#[derive(Deserialize, Serialize)] +pub struct Conjunction { + /// A logical "or" operation between a number of literals. + /// + /// It evaluates to `true` for a solution iff at least one of the literals evaluates to `true`. + pub or: [Literal; 3], +} +impl Conjunction { + pub fn variables(&self) -> impl Iterator { + self.or.iter().map(|literal| &literal.variable) + } + pub fn eval(&self, env: &Env) -> bool { + self.or.iter().any(|literal| literal.eval(env)) + } +} + +/// Either a variable or a negated variable. +pub struct Literal { + /// The name of the variable. + pub variable: Variable, + + /// If `true`, this literal represents the variable itself and it evaluates to `true` iff + /// the variable is assigned to `true`. Respectively, if `false`, this literal represents + /// the negation of the variable and it evaluates to `true` iff the variable is assigned + /// to `false`. + pub positive: bool, +} +impl Literal { + pub fn eval(&self, env: &Env) -> bool { + if self.positive { + self.variable.eval(env) + } else { + self.variable.eval(env).not() + } + } +} + +impl Display for Literal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.positive { + self.variable.0.fmt(f) + } else { + write!(f, "!{}", self.variable.0) + } + } +} +impl<'de> Deserialize<'de> for Literal { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + const NOT: &str = "NOT "; + let text = String::deserialize(deserializer)?; + let (positive, name) = match text.strip_prefix(NOT) { + None => (true, Cow::from(text)), + Some(suffix) => (false, Cow::from(suffix)), + }; + Ok(Literal { + positive, + variable: Variable(name.trim().into()), + }) + } +} +impl Serialize for Literal { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.positive { + self.variable.0.serialize(serializer) + } else { + format!("NOT {}", self.variable.0).serialize(serializer) + } + } +} + +/// The name of a variable. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone)] +pub struct Variable(Arc); +impl Variable { + pub fn positive(&self) -> Literal { + Literal { + variable: self.clone(), + positive: true, + } + } + + pub fn negative(&self) -> Literal { + Literal { + variable: self.clone(), + positive: false, + } + } + + pub fn eval(&self, env: &Env) -> bool { + *env.0.get(self).unwrap() + } +} + +pub struct Env(HashMap); + +impl Input { + pub fn variables(&self) -> impl Iterator { + self.and.iter().flat_map(|c| c.variables()).dedup() + } + + pub fn ordered_variables(&self) -> impl Iterator { + self.variables().sorted().dedup() + } + + pub fn to_qubo(&self) -> Constraints { + // Collect variables and assign to each an index. + let variables: HashMap<&Variable, usize> = self + .ordered_variables() + .enumerate() + .map(|(i, v)| (v, i)) + .collect(); + let num_nodes = self.and.len() + variables.len(); + let mut constraints = Constraints::new(num_nodes); + for (conj_offset, conjunction) in self.and.iter().enumerate() { + // A conjunction Ci = L1 && L2 && L3 is compiled into + // - ((1 + Ci)(L1 + L2 + L3) - L1.L2 - L1.L3 - L2.L3 - 2 . Ci) + // -L1 + -L2 + -L3 + -Ci.L1 + -Ci.L2 + -Ci.L3 + L1.L2 + L1.L3 + L2.L3 + 2.Ci + let conj_index = conj_offset + self.and.len() - 1; + let mut delta_conj_var = 2.0; + eprintln!("conjunction {} => index {}", conj_offset, conj_index); + for (literal_1_index, literal_1) in conjunction.or.iter().enumerate() { + let var_1_index = variables.get(&literal_1.variable).cloned().unwrap(); + eprintln!("literal {} => index {}", literal_1.variable.0, var_1_index); + + // Encode term `-Lj`. + let delta_var_1 = if literal_1.positive { -1.0 } else { 1.0 }; + constraints + .delta_at(var_1_index, var_1_index, delta_var_1) + .unwrap(); + + // Encode term `Lj.Lk` for k > j. + for literal_2 in conjunction.or.iter().skip(literal_1_index + 1) { + let var_2_index = variables.get(&literal_2.variable).cloned().unwrap(); + let (delta_product_2, delta_var_1, delta_var_2) = + match (literal_1.positive, literal_2.positive) { + (true, true) => { + //Vj.Vk + (1.0, 0.0, 0.0) + } + (true, false) => { + // (Vj.(1 - Vk)) = Vj - Vj.Vk + (-1.0, 1.0, 0.0) + } + (false, true) => { + // ((1 - Vj).Vk) = Vk - Vj.Vk + (-1.0, 0.0, 1.0) + } + (false, false) => { + // (1 - Vj).(1 - Vk) = 1 - Vj - Vk + Vj.Vk + (1.0, -1.0, -1.0) + } + }; + constraints + .delta_at(var_1_index, var_1_index, delta_var_1) + .unwrap(); + constraints + .delta_at(var_2_index, var_2_index, delta_var_2) + .unwrap(); + constraints + .delta_at(var_1_index, var_2_index, delta_product_2) + .unwrap(); + } + + // Encode term `-Ci.Lj` + eprintln!("product3 {}, {}", var_1_index, conj_index); + let (delta_prod_3, additional_delta_conj_var) = if literal_1.positive { + (-1.0, 0.0) + } else { + (1.0, -1.0) + }; + constraints + .delta_at(var_1_index, conj_index, delta_prod_3) + .unwrap(); + + delta_conj_var += additional_delta_conj_var; + } + + // Encode term `2.Ci` + eprintln!("C[{}]", conj_offset); + constraints + .delta_at(conj_index, conj_index, delta_conj_var) + .unwrap(); + } + constraints + } +} + +/// Test against the sample at https://canvas.auckland.ac.nz/courses/14782/files/574983/download?verifier=1xqRikUjTEBwm8PnObD8YVmKdeEhZ9Ui8axW8HwP&wrap=1 +#[test] +fn test_to_qubo() { + //(x_1 OR x_2 OR x_3) AND (NEG(x_1) OR x_2 OR x_3) AND (x_1 OR NEG(x_2) OR x_3) AND (NEG(x_1) OR x_2 OR NEG(x_3)) + let x_1 = Variable("x_1".into()); + let x_2 = Variable("x_2".into()); + let x_3 = Variable("x_3".into()); + let input = Input { + and: vec![ + Conjunction { + or: [x_1.positive(), x_2.positive(), x_3.positive()], + }, + Conjunction { + or: [x_1.negative(), x_2.positive(), x_3.positive()], + }, + Conjunction { + or: [x_1.positive(), x_2.negative(), x_3.positive()], + }, + Conjunction { + or: [x_1.negative(), x_2.positive(), x_3.negative()], + }, + ], + }; + let constraints = input.to_qubo(); + assert_eq!(constraints.num_nodes(), 7); // 3 SAT variables, 4 terms. + let expected = Constraints::from_const([ + [0., 0., 0., 0., 0., 0., 0.], + [-2., 1., 0., 0., 0., 0., 0.], + [2., 0., -1., 0., 0., 0., 0.], + [-1., -1., -1., 2., 0., 0., 0.], + [1., -1., -1., 0., 1., 0., 0.], + [-1., 1., -1., 0., 0., 1., 0.], + [1., -1., 1., 0., 0., 0., 0.], + ]); + for i in 0..constraints.num_nodes() { + for j in 0..constraints.num_nodes() { + assert_eq!( + constraints.at(i, j).unwrap(), + expected.at(i, j).unwrap(), + "({}, {})", + i, + j + ); + } + } +} + +impl Input { + /// Do... something with the results. + pub fn handle_results(&self, results: &[Sample]) -> Result<(), anyhow::Error> { + assert!(!results.is_empty()); + let variables = self.ordered_variables().collect_vec(); + let mut env = Env(HashMap::new()); + for result in results { + eprintln!("Instances {}", result.instances); + let mut writer = csv::Writer::from_writer(std::io::stdout()); + for (c, var) in result.bitstring.chars().zip(variables.iter()) { + // Show result. + #[derive(Serialize)] + struct Record<'a> { + variable: &'a str, + value: char, + } + let record = Record { + variable: var.0.as_ref(), + value: c, + }; + writer.serialize(record)?; + + // Prepare to check result. + env.0.insert((*var).clone(), c == '1'); + } + + // Double-check that the result is actually meaningful. + if self.eval(&env) { + println!("=> 1 [PASS]"); + } else { + println!("=> 0 [FAIL]"); + } + } + Ok(()) + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs new file mode 100644 index 0000000..fac68d9 --- /dev/null +++ b/src/frontend/mod.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +use crate::{backend, runtime::run::Sample}; + +pub mod max3sat; + +/// Formats understood by the various frontends. +/// +/// As of this writing, we read everything from yaml source files. +#[derive(Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum Input { + /// Raw QUBO constraints. + #[serde(rename = "qubo")] + Qubo(backend::qubo::Constraints), + + /// An input for maximal 3SAT. + #[serde(rename = "max3sat")] + Max3Sat(max3sat::Input), +} + +impl Input { + /// Compile the input to a set of QUBO constraints. + pub fn to_constraints(&self) -> Result { + match *self { + Self::Max3Sat(ref input) => Ok(input.to_qubo()), + Self::Qubo(ref input) => Ok(input.clone()), + } + } +} + +impl Input { + /// Process results obtained from the QPU or emulator. + /// + /// Typically, use the input to turn them into something human-readable. + pub fn handle_results(&self, samples: &[Sample]) -> Result<(), anyhow::Error> { + match *self { + Self::Max3Sat(ref input) => input.handle_results(samples), + Self::Qubo(_) => { + let mut writer = csv::Writer::from_writer(std::io::stdout()); + for record in samples { + writer.serialize(record)?; + } + Ok(()) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index bc8e363..b813694 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ -pub mod device; -pub mod pulser; -pub mod qaa; -pub mod qubo; +pub mod backend; +pub mod frontend; +pub mod path; +pub mod runtime; +pub mod studio; pub mod types; diff --git a/src/main.rs b/src/main.rs index ae1378d..d23cbe1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,16 @@ -use std::path::PathBuf; +use std::{fmt::Display, path::PathBuf}; use clap::Parser; use qlafoutea::{ - device::Device, - qaa, - qubo::{self, format}, + backend::{device::Device, format::Code, qaa, qubo}, + path::PathExt, + runtime, types::Quality, }; #[derive(clap::Parser, Debug)] -struct Args { +struct Build { /// The file to compile. - #[arg(long)] source: PathBuf, /// A seed to use for random number generation. @@ -30,39 +29,99 @@ struct Args { /// register. #[arg(long, default_value_t = 4_000)] max_iters: u64, + + /// While laying out qubo, we make it costly to place atoms too close to the physical limits + /// of the device. This value determines how much we worry when atoms are laid out in the + /// unacceptable zone. + #[arg(long, default_value_t = 1_000.)] + overflow_protection_factor: f64, + + /// While laying out qubo, we make it costly to place atoms too close to the physical limits + /// of the device. This value determines how close is acceptable, with `0` meaning "start + /// worrying immediately` and `1` meaning "start worrying only if the atoms are beyond the + /// physical limits of the device". + #[arg(long, default_value_t = 0.95)] + overflow_protection_threshold: f64, } -fn main() -> Result<(), anyhow::Error> { - let args = Args::parse(); +#[derive(clap::ValueEnum, Clone, Copy, Debug)] +enum Runner { + PyPulser, + PulserStudio, +} +impl Display for Runner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Self::PyPulser => write!(f, "python-pulser"), + Self::PulserStudio => write!(f, "pulser-studio"), + } + } +} + +#[derive(clap::Parser, Debug)] +struct Run { + /// The file to run. + /// + /// It must have been compiled already. + source: PathBuf, + + /// How many results to display. + /// + /// If this value is in [0., 1.], discard any result + /// if the number of samples in which it appears is < + /// best result * result_sample_threshold. + #[arg(long, default_value_t = 0.5)] + result_sample_threshold: f64, + + #[arg(long, default_value_t = Runner::PulserStudio)] + runner: Runner, +} + +#[derive(Debug, Parser)] +enum Command { + /// Build from high-level code. + Build(Build), + + /// Launch a previously built program. + Run(Run), +} + +fn build(args: Build) -> Result<(), anyhow::Error> { let device = Device::analog(); let path_source = args.source.as_path(); - let qubo_source = std::fs::File::open(path_source).expect("Failed to open source file"); - let qubo_parsed = serde_yaml::from_reader::<_, format::Format>(qubo_source) + // Step: parse source. + let source = std::fs::File::open(path_source).expect("Failed to open source file"); + let problem = serde_yaml::from_reader::<_, qlafoutea::frontend::Input>(source) .expect("Failed to parse source file"); - let constraints = qubo_parsed.as_constraints(); + + // Step: compile to qubo. + let constraints = problem.to_constraints().expect("Failed to compile to QUBO"); // Step: compile the qubo to a register. - eprintln!("...compiling {} constraints", constraints.len()); - let (register, quality) = constraints + eprintln!("...compiling {} constraints", constraints.num_constraints()); + let (register, quality, seed) = constraints .layout( &device, &qubo::Options { seed: args.seed, min_quality: Quality::new(args.min_quality), max_iters: args.max_iters, + overflow_protection_factor: args.overflow_protection_factor, + overflow_protection_threshold: args.overflow_protection_threshold, }, ) .expect("Failed to compile qubo"); eprintln!( - "...compiled to {} qubits with a quality of {}", + "...compiled to {} qubits with a quality of {} (using seed {})", register.len(), - quality + quality, + seed ); // Step: integrate QAA. let sequence = qaa::compile( - constraints, + &constraints, device, register, &qaa::Options { @@ -70,16 +129,42 @@ fn main() -> Result<(), anyhow::Error> { }, ); + // Step: write "bytecode". + let code = Code::try_new(problem, sequence).expect("Couldn't generate code"); + // Write pulser output. // In the future, we'll probably write more data in the file. - let mut path_dest = PathBuf::new(); - path_dest.set_file_name(path_source.file_name().unwrap()); - path_dest = match path_source.extension() { - None => path_dest.with_extension("pulser.json"), - Some(ext) => path_dest.with_extension(format!("{}.pulser.json", ext.to_string_lossy())), - }; + let path_dest = path_source.here_with_ext("qlaf"); eprintln!("...generating {}", path_dest.display()); let out_dest = std::fs::File::create(path_dest)?; - serde_json::to_writer_pretty(out_dest, &sequence)?; + serde_json::to_writer_pretty(out_dest, &code)?; Ok(()) } + +fn run(args: Run) -> Result<(), anyhow::Error> { + eprintln!("...loading code"); + let input = std::fs::File::open(args.source).expect("Failed to open code"); + let code: Code = serde_yaml::from_reader(input).expect("Failed to parse code"); + + eprintln!("...starting emulation"); + runtime::run::run( + code, + runtime::run::Options { + result_sample_threshold: args.result_sample_threshold, + runner: match args.runner { + Runner::PulserStudio => runtime::run::Runner::PulserStudio, + Runner::PyPulser => runtime::run::Runner::PyPulser, + }, + }, + )?; + + Ok(()) +} + +fn main() -> Result<(), anyhow::Error> { + let args = Command::parse(); + match args { + Command::Build(args) => build(args), + Command::Run(args) => run(args), + } +} diff --git a/src/path/mod.rs b/src/path/mod.rs new file mode 100644 index 0000000..d00c2c0 --- /dev/null +++ b/src/path/mod.rs @@ -0,0 +1,21 @@ +use std::path::{Path, PathBuf}; + +pub trait PathExt { + fn here_with_ext(&self, extension: &str) -> PathBuf; +} + +impl PathExt for PathBuf { + fn here_with_ext(&self, extension: &str) -> PathBuf { + let mut buf = PathBuf::new(); + buf.set_file_name(self.file_name().unwrap()); + buf.with_extension(extension) + } +} + +impl PathExt for &Path { + fn here_with_ext(&self, extension: &str) -> PathBuf { + let mut buf = PathBuf::new(); + buf.set_file_name(self.file_name().unwrap()); + buf.with_extension(extension) + } +} diff --git a/src/pulser/sequence.rs b/src/pulser/sequence.rs deleted file mode 100644 index 5a36426..0000000 --- a/src/pulser/sequence.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::{collections::HashMap, rc::Rc}; - -use serde::Serialize; - -use crate::{ - device::Device, - pulser::{device::ChannelId, pulse::Pulse, register::Register}, -}; - -pub struct Sequence { - register: Rc, - device: Rc, - pulse: Rc, - channels: Vec>, -} - -impl Sequence { - pub fn new(device: Device, register: Register, pulse: Pulse, channels: &[Rc]) -> Self { - Self { - register: Rc::new(register), - device: Rc::new(device), - pulse: Rc::new(pulse), - channels: channels.to_vec(), - } - } -} - -impl Serialize for Sequence { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let schema = Schema { - version: "1", - name: "qlafoutea compilation target", - register: self.register.clone(), - device: self.device.clone(), - variables: HashMap::new(), - operations: vec![self.pulse.clone()], - measurement: None, - channels: self - .channels - .iter() - .map(|chan| (chan.to_string(), ChannelId(chan.to_string()))) - .collect(), - }; - schema.serialize(serializer) - } -} - -#[derive(Serialize)] -struct Schema { - version: &'static str, - variables: HashMap, // always empty for the time being. - register: Rc, - device: Rc, - name: &'static str, - operations: Vec>, - channels: HashMap, - measurement: Option<()>, // always None for the time being. -} diff --git a/src/qubo/format.rs b/src/qubo/format.rs deleted file mode 100644 index eb70cc7..0000000 --- a/src/qubo/format.rs +++ /dev/null @@ -1,52 +0,0 @@ -use super::Constraints; -use serde::Deserialize; -use serde_yaml::with::singleton_map; - -/// The (de)serialization format for qubo constraints. -#[derive(Deserialize)] -#[serde(tag = "version")] -pub enum Format { - /// Version one of the serialization format. - #[serde(rename = "0.1.0", with = "singleton_map")] - V1(Constraints), -} - -impl Format { - pub fn as_constraints(&self) -> &Constraints { - match *self { - Self::V1(ref c) => c, - } - } -} - -/* -// Very early draft on Format v2. -impl<'de> serde::Deserialize<'de> for Format { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct Aux { - constraints: Vec>, - } - let aux = Aux::deserialize(deserializer)?; - let num_nodes = aux.constraints.len(); - let mut constraints = vec![]; - for (index, line) in aux.constraints.iter().enumerate() { - if line.len() + index != num_nodes { - return Err(D::Error::invalid_value(Unexpected::Str(format!("{}", line.iter().format(", ")).as_str()), &format!("since this is a QUBO with {num_nodes} nodes, line {index} should specify {} constraints", - num_nodes - index).as_str())); - } - for i in 1..=index { - constraints.push(aux.constraints[0][i]); - } - constraints.extend(line); - } - Ok(Constraints { - data: constraints, - num_nodes, - }) - } -} - */ diff --git a/src/qubo/mod.rs b/src/qubo/mod.rs deleted file mode 100644 index cc141f5..0000000 --- a/src/qubo/mod.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! QUBO format as an intermediate language -//! -//! QUBO (Quadratic unconstrained binary optimization) is an optimization problem -//! that generally maps well to quantum registers. -//! -//! Given a set of weights Q (a matrix), solving the QUBO problem means finding -//! the best values x in {0, 1} ^ n to minimize the following formula: -//! -//! sum_{i, j}(Q[i, j] * x [i] * x[j]) - -use argmin::{ - core::{CostFunction, Executor}, - solver::neldermead::NelderMead, -}; -use itertools::Itertools; -use medians::Medianf64; -use rand::{Rng, SeedableRng}; -use rayon::prelude::*; -use serde::Deserialize; - -use crate::{ - device::Device, pulser::register::Register, types::units::Coordinates, - types::units::Micrometers, types::Quality, -}; - -pub mod format; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("index out of bounds")] - IndexOutOfBounds { - x: usize, - y: usize, - num_nodes: usize, - }, - - #[error("layout error #?")] - Layout(argmin::core::Error), - - #[error("failed to layout")] - NoSolution, -} - -pub struct Options { - pub seed: u64, - pub min_quality: Quality, - pub max_iters: u64, -} - -/// A set of qubo constraints. -/// -/// For (de)serialization, please use `format::Format`. -#[derive(Debug, Deserialize)] -pub struct Constraints { - // FIXME: Check that there is no NaN, no infinite, that all values are reasonable. - /// A symmetric matrix of weights of size `num_nodes`. - /// - /// In practice, the sub-diagonal part of the matrix is redundant. - /// We could save ~half of the memory by removing this part, but at - /// the expense of increasing computation costs. - /// - /// In this implementation, we don't make the effort. - data: Vec, - num_nodes: usize, -} -#[allow(clippy::len_without_is_empty)] -impl Constraints { - #[allow(dead_code)] - pub fn try_new(num_nodes: usize, data: Vec) -> Option { - if data.len() != num_nodes * num_nodes { - return None; - } - Some(Self { data, num_nodes }) - } - - /// Return the number of constraints. - pub fn len(&self) -> usize { - self.num_nodes * self.num_nodes / 2 - } - - pub fn layout(&self, device: &Device, options: &Options) -> Option<(Register, Quality)> { - (options.seed..std::u64::MAX) - .into_par_iter() - .find_map_any(|seed| { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - // Set initial search points. - // - // Our search space has 2 * num_node dimensions (we're looking for 2 coordinates per node). - // By definition, Nelder-Mead must take dimensions + 1 starting points. - // - // Since we wish to be reproducible, we initialize these points from `rng`, which can be - // seeded by the caller. - let mut params = Vec::with_capacity(self.num_nodes * 2 + 1); - for _ in 0..self.num_nodes { - let mut state = vec![0f64; self.num_nodes * 2]; - rng.fill(state.as_mut_slice()); - params.push(state); - } - let solver = NelderMead::new(params); - - let cost = Cost { - constraints: self, - device, - }; - - let optimized = Executor::new(cost, solver) - .configure(|state| state.max_iters(options.max_iters).target_cost(1e-6)) - .run() - .expect("Error in the execution of register optimizer"); - let quality = 1. - optimized.state.best_cost.atan() / std::f64::consts::FRAC_PI_2; - let quality = Quality::new(quality); - let coordinates = match optimized.state.best_param { - None => return None, - Some(v) => { - assert!(v.len() % 2 == 0); - let mut iter = v.into_iter(); - let mut coordinates = Vec::with_capacity(self.num_nodes); - while let Some((x, y)) = iter.next_tuple() { - coordinates.push(Coordinates::::new(x, y)) - } - coordinates - } - }; - let register = Register { - coordinates: coordinates.into(), - }; - - if quality >= options.min_quality { - eprintln!("succeeded with seed {seed}"); - Some((register, quality)) - } else { - None - } - }) - } - - pub fn omega(&self) -> f64 { - self.data - .iter() - .cloned() - .filter(|f| *f > 0.) - .collect_vec() - .medf_unchecked() - } -} - -impl Constraints { - pub fn at(&self, x: usize, y: usize) -> Result { - let index = self.index(x, y)?; - Ok(self.data[index]) - } - - fn index(&self, x: usize, y: usize) -> Result { - let (x, y) = if x >= self.num_nodes || y >= self.num_nodes { - return Err(Error::IndexOutOfBounds { - x, - y, - num_nodes: self.num_nodes, - }); - } else if x <= y { - (x, y) - } else { - (y, x) - }; - Ok(self.num_nodes * y + x) - } -} - -struct Cost<'a> { - constraints: &'a Constraints, - device: &'a Device, -} -impl<'a> Cost<'a> { - fn actual_interaction( - &self, - first: Coordinates, - second: Coordinates, - ) -> f64 { - self.device - .interaction_coeff() - .value_rad_per_us_times_us_64() - / first.sqdist(&second).powi(3) - } - fn expected_interaction(&self, x: usize, y: usize) -> f64 { - self.constraints.at(x, y).unwrap() - } -} - -impl<'a> CostFunction for Cost<'a> { - type Param = Vec; - - type Output = f64; - - fn cost(&self, param: &Self::Param) -> Result { - debug_assert_eq!(param.len(), 2 * self.constraints.num_nodes); - let mut total = 0.; - - for i in 0..self.constraints.num_nodes { - let first = Coordinates::::new(param[2 * i], param[2 * i + 1]); - for j in i + 1..self.constraints.num_nodes { - let second = Coordinates::::new(param[2 * j], param[2 * j + 1]); - let actual_interaction = self.actual_interaction(first, second); - let expected_interaction = self.expected_interaction(i, j); - let diff = (actual_interaction - expected_interaction).powi(2); - if i == j { - total += diff - } else { - total += 2. * diff - } - } - } - let result = total.sqrt(); - Ok(result) - } -} - -#[test] -// Test that the cost is roughly the same to the one we compute in Python. -fn test_cost_function_vs_python() { - let evaluator = Cost { - device: &Device::analog(), - constraints: &Constraints::try_new( - 5, - vec![ - -10.0, - 19.7365809, - 19.7365809, - 5.42015853, - 5.42015853, - 19.7365809, - -10.0, - 20.67626392, - 0.17675796, - 0.85604541, - 19.7365809, - 20.67626392, - -10.0, - 0.85604541, - 0.17675796, - 5.42015853, - 0.17675796, - 0.85604541, - -10.0, - 0.32306662, - 5.42015853, - 0.85604541, - 0.17675796, - 0.32306662, - -10.0, - ], - ) - .unwrap(), - }; - let param = vec![ - 0.5488135, 0.71518937, 0.60276338, 0.54488318, 0.4236548, 0.64589411, 0.43758721, 0.891773, - 0.96366276, 0.38344152, - ]; - let cost = evaluator.cost(¶m).unwrap(); - let python_cost = 149417981060.90808; // Achieved manually from running Python implementation. - let diff = f64::abs(cost - python_cost) / f64::max(cost, python_cost); - assert!( - diff <= 0.01, - "Expected < 1% difference between {} and {}, got {}", - cost, - python_cost, - diff - ); -} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs new file mode 100644 index 0000000..9137f27 --- /dev/null +++ b/src/runtime/mod.rs @@ -0,0 +1 @@ +pub mod run; diff --git a/src/runtime/run.rs b/src/runtime/run.rs new file mode 100644 index 0000000..298fe4c --- /dev/null +++ b/src/runtime/run.rs @@ -0,0 +1,114 @@ +use std::collections::HashMap; + +use anyhow::Context; +use itertools::Itertools; +use pyo3::{ + types::{PyAnyMethods, PyDict}, + Bound, Python, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + backend::{format::Code, pulser::sequence::Sequence}, + studio, +}; + +pub enum Runner { + PyPulser, + PulserStudio, +} + +pub struct Options { + /// How many results to display. + /// + /// If this value is in [0., 1.], discard any result + /// if the number of samples in which it appears is < + /// best result * result_sample_threshold. + pub result_sample_threshold: f64, + + pub runner: Runner, +} + +pub fn run(code: Code, options: Options) -> Result<(), anyhow::Error> { + let mut sorted_samples = match options.runner { + Runner::PyPulser => run_python(&code.sequence)?, + Runner::PulserStudio => run_studio(&code.sequence)?, + }; + + // Only keep the best entries. + let maybe_cut_at = if let Some(best) = sorted_samples.first() { + if let Some((first, _)) = sorted_samples.iter().find_position(|sample| { + (sample.instances as f64 / best.instances as f64) < options.result_sample_threshold + }) { + Some(first) + } else { + None + } + } else { + None + }; + if let Some(cut_at) = maybe_cut_at { + sorted_samples.resize_with(cut_at, || panic!()); + } + + code.problem.handle_results(&sorted_samples)?; + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Sample { + pub bitstring: String, + pub instances: u64, +} + +pub fn run_python(source: &str) -> Result, anyhow::Error> { + let Some(ref executable) = pyo3_build_config::get().executable else { + return Err(anyhow::anyhow!("Cannot find a Python environment")); + }; + let mut cmd = std::process::Command::new(executable); + cmd.args(["-m", "pip", "install", "pulser"]); + cmd.spawn() + .context("Failed to launch setup")? + .wait() + .context("Error while setting up dependencies")?; + + pyo3::prepare_freethreaded_python(); + let samples = Python::with_gil(|py| -> Result, pyo3::PyErr> { + let sequence_builder = py.import_bound("pulser")?.getattr("Sequence")?; + let qutip_emulator = py + .import_bound("pulser_simulation")? + .getattr("QutipEmulator")?; + let sequence = sequence_builder.call_method1("from_abstract_repr", (source,))?; + let simulator = qutip_emulator.call_method1("from_sequence", (sequence,))?; + let result = simulator.call_method0("run")?; + let np_samples: Bound = result.call_method0("sample_final_state")?.extract()?; + + let mut samples = HashMap::new(); + for (k, v) in np_samples { + let k: String = k.extract()?; + let v: u64 = v.extract()?; + samples.insert(k, v); + } + Ok(samples) + }) + .context("Failed to run simulator")?; + let sorted = samples + .into_iter() + .sorted_by(|a, b| Ord::cmp(&b.1, &a.1)) + .map(|(bitstring, instances)| Sample { + bitstring, + instances, + }) + .collect_vec(); + + eprintln!("simulation complete"); + Ok(sorted) +} + +pub fn run_studio(source: &str) -> Result, anyhow::Error> { + let sequence: Sequence = serde_json::from_str(source).context("Invalid sequence")?; + let studio = studio::Runner::new()?; + let mut simulator = studio.simulator()?; + simulator.simulate_sequence(sequence)?; + unimplemented!() +} diff --git a/src/studio/mod.rs b/src/studio/mod.rs new file mode 100644 index 0000000..ad713b0 --- /dev/null +++ b/src/studio/mod.rs @@ -0,0 +1,338 @@ +#![allow(dead_code)] +#![allow(clippy::unused_unit)] + +use anyhow::Context; +use wasmtime::{Caller, Config, Engine, Instance, Linker, Module, Store, TypedFunc, Val}; + +use crate::backend::pulser::sequence::Sequence; + +/// Bindings for Pulser studio. + +pub struct Runner { + engine: Engine, + module: Module, +} +impl Runner { + pub fn new() -> Result { + let mut config = Config::new(); + #[cfg(debug_assertions)] + { + config.debug_info(true).wasm_backtrace(true); + } + + let engine = Engine::new(&config) + .context("Could not initialize engine to compile Pulser studio simulator")?; + + // FIXME: Cache compilation. + let module = Module::new(&engine, include_bytes!("pulser_wasm_bg.wasm")) + .context("Failed to compile Pulser studio simulator")?; + Ok(Runner { engine, module }) + } + pub fn simulator(&self) -> Result { + //let _foo = || unimplemented!(); + let mut linker = Linker::new(&self.engine); + + /* + (import "wbg" "__wbg_new_d3138911a89329b0" (func (;7;) (type 9))) + (import "wbg" "__wbg_buffer_5e74a88a1424a2e0" (func (;9;) (type 3))) + (import "wbg" "__wbg_newwithbyteoffsetandlength_f6c2c5e40f6f5bda" (func (;10;) (type 2))) + (import "wbg" "__wbg_new_86a3fd385f9bcaf2" (func (;11;) (type 3))) + (import "wbg" "__wbg_newwithbyteoffsetandlength_ad2916c6fa7d4c6f" (func (;12;) (type 2))) + (import "wbg" "__wbg_new_f5438c0cea22a3aa" (func (;13;) (type 3))) + (import "wbg" "__wbindgen_throw" (func (;14;) (type 1))) + (import "wbg" "__wbindgen_memory" (func (;15;) (type 9)) + */ + + linker.func_wrap( + "wbg", + "__wbindgen_string_new", + |mut _caller: Caller<'_, State>, _a: i32, _b: i32| -> () { + unimplemented!(); + }, + )?; + + linker.func_wrap( + "wbg", + "__wbg_set_327aa1a19c3f2018", + |_: i32, _: i32, _: i32| -> i32 { + //getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + unimplemented!(); + }, + )?; + + linker.func_wrap( + "wbg", + "__wbindgen_object_drop_ref", + |mut caller: Caller<'_, State>, index: i32| { + caller.data_mut().drop_ref(index); + }, + )?; + linker.func_wrap( + "wbg", + "__wbindgen_number_new", + |mut caller: Caller<'_, State>, value: f64| -> i32 { + caller + .data_mut() + .add_heap_object(Value::Wasm(Val::F64(f64::to_bits(value)))) + as i32 + }, + )?; + linker.func_wrap( + "wbg", + "__wbindgen_object_clone_ref", + |mut caller: Caller<'_, State>, idx: i32| -> i32 { + let value = caller.data_mut().heap[idx as usize].unwrap(); + caller.data_mut().add_heap_object(value) as i32 + }, + )?; + linker.func_wrap( + "wbg", + "__wbg_new_693216e109162396", + |mut caller: Caller<'_, State>| -> i32 { + caller.data_mut().add_heap_object(Value::Error) as i32 + }, + )?; + linker.func_wrap( + "wbg", + "__wbg_stack_0ddaca5d1abfb52f", + |_caller: Caller<'_, State>, _idx: i32, _offset: i32| -> () { + /* + var ret = getObject(arg1).stack; + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + */ + // As far as I can tell, this stores the stack of a JS error in memory that we never actually + // look at. Let's see what happens if we do nothing here! + panic!("__wbg_stack_0ddaca5d1abfb52f"); + }, + )?; + + linker.func_wrap( + "wbg", + "__wbg_error_09919627ac0992f5", + |_caller: Caller<'_, State>, _idx: i32, _offset: i32| -> () { + // This should probably just display an error. + panic!("__wbg_error_09919627ac0992f5"); + }, + )?; + + let mut store = Store::new(&self.engine, State::new()); + let instance = linker + .instantiate(&mut store, &self.module) + .context("Failed to initialize instance of Pulser studio simulator")?; + let exports = Exports { + __wbg_simbuilder_free: instance + .get_typed_func(&mut store, "__wbg_simbuilder_free") + .unwrap(), + simbuilder_new: instance + .get_typed_func(&mut store, "simbuilder_new") + .unwrap(), + simbuilder_set_step_time: instance + .get_typed_func(&mut store, "simbuilder_set_step_time") + .unwrap(), + simbuilder_set_rydberg_level: instance + .get_typed_func(&mut store, "simbuilder_set_rydberg_level") + .unwrap(), + simbuilder_add_atom: instance + .get_typed_func(&mut store, "simbuilder_add_atom") + .unwrap(), + simbuilder_add_run: instance + .get_typed_func(&mut store, "simbuilder_add_run") + .unwrap(), + simbuilder_build: instance + .get_typed_func(&mut store, "simbuilder_build") + .unwrap(), + __wbg_simdata_free: instance + .get_typed_func(&mut store, "__wbg_simdata_free") + .unwrap(), + simdata_step: instance.get_typed_func(&mut store, "simdata_step").unwrap(), + simdata_get_data: instance + .get_typed_func(&mut store, "simdata_get_data") + .unwrap(), + __wbindgen_malloc: instance + .get_typed_func(&mut store, "__wbindgen_malloc") + .unwrap(), + __wbindgen_free: instance + .get_typed_func(&mut store, "__wbindgen_free") + .unwrap(), + __wbindgen_realloc: instance + .get_typed_func(&mut store, "__wbindgen_realloc") + .unwrap(), + }; + Simulator::new(instance, exports, store) + } +} + +#[derive(Clone)] +enum Value { + Wasm(Val), + Error, // FIXME: This might need a property `stack`. +} + +enum Cell { + Empty { next: usize }, + Full(Value), +} +impl Cell { + pub fn unwrap(&self) -> Value { + match self { + Cell::Full(val) => val.clone(), + _ => panic!(), + } + } +} +struct State { + heap: Vec, + next: usize, +} +impl State { + pub fn new() -> Self { + // heap.push(undefined, null, true, false); + let heap = Vec::with_capacity(32); + State { + next: heap.len(), + heap, + } + } + pub fn drop_ref(&mut self, index: i32) -> Value { + let index = index as usize; + let cell = &mut self.heap[index]; + if index < 36 { + // Hardcoded value, generated by wasm-bindgen + return cell.unwrap(); + } + let mut swap = Cell::Empty { next: self.next }; + std::mem::swap(cell, &mut swap); + self.next = index; + cell.unwrap() + } + pub fn add_heap_object(&mut self, value: Value) -> usize { + if self.next == self.heap.len() { + self.heap.push(Cell::Empty { + next: self.heap.len() + 1, + }); + } + let idx = self.next; + match self.heap[idx] { + Cell::Full(_) => panic!(), + Cell::Empty { next } => { + self.heap[idx] = Cell::Full(value); + self.next = next; + } + } + idx + } + pub fn take_object(&mut self, index: i32) -> Value { + self.drop_ref(index) + } +} + +struct Exports { + /// func 118 + /// + /// simbuilder:InnerRef -> void + __wbg_simbuilder_free: TypedFunc, + + /// func 101 + /// + /// void -> simbuilder:InnerRef + simbuilder_new: TypedFunc<(), i32>, + + /// func 157 + /// + /// simbuilder:InnerRef, f32 -> void + simbuilder_set_step_time: TypedFunc<(i32, f32), ()>, + + /// func 143 + /// + /// simbuilder:InnerRef, level:i32 -> void + simbuilder_set_rydberg_level: TypedFunc<(i32, i32), ()>, + + /// func 112 + /// + /// simbuilder:InnerRef, x:f32, y:f32 -> void + simbuilder_add_atom: TypedFunc<(i32, f32, f32), ()>, + + /// func 107 + /// + /// simbuilder:InnerRef, basis:i32 !isLikeNone:i32 address||0:i32 f32ArrayPtr:i32 WASM_VECTOR_LEN:i32 -> void + simbuilder_add_run: TypedFunc<(i32, i32, i32, i32, i32, i32), ()>, + + /// func 58 + /// + /// simbuilder:InnerRef -> simdata:InnerRef + simbuilder_build: TypedFunc, + + /// func 36 + /// + /// simdata:InnerRef -> void + __wbg_simdata_free: TypedFunc, + + /// func 77 + /// + /// simdata:InnerRef, microsteps:i32 -> bool + simdata_step: TypedFunc<(i32, i32), i32>, + + /// func 148 + /// + /// simdata:InnerRef, step:i32 -> InnerRef:SimulationData + simdata_get_data: TypedFunc<(i32, i32), i32>, + + /// func 158 + /// + /// size:i32 -> InnerRef + __wbindgen_malloc: TypedFunc, + + /// func 197 + /// + /// InnerRef:i32, len:i32 -> void + __wbindgen_free: TypedFunc<(i32, i32), ()>, + + /// func 175 + /// + /// i32, i32, i32 -> i32 + __wbindgen_realloc: TypedFunc<(i32, i32, i32), i32>, +} + +pub struct Simulator { + instance: Instance, + exports: Exports, + store: Store, + /// The in-store address for the builder. + builder_addr: i32, +} +impl Simulator { + fn new( + instance: Instance, + exports: Exports, + mut store: Store, + ) -> Result { + let builder_addr = exports.simbuilder_new.call(&mut store, ())?; + Ok(Self { + instance, + exports, + store, + builder_addr, + }) + } + + pub fn simulate_sequence(&mut self, sequence: Sequence) -> Result<(), anyhow::Error> { + self.exports.simbuilder_set_rydberg_level.call( + &mut self.store, + (self.builder_addr, sequence.device().rydberg_level() as i32), + )?; + for atom in sequence.register().coordinates.as_ref() { + self.exports.simbuilder_add_atom.call( + &mut self.store, + ( + self.builder_addr, + atom.x.into_inner() as f32, + atom.y.into_inner() as f32, + ), + )?; + } + unimplemented!() + } +} diff --git a/src/studio/pulser_wasm_bg.wasm b/src/studio/pulser_wasm_bg.wasm new file mode 100644 index 0000000..ab95d2e Binary files /dev/null and b/src/studio/pulser_wasm_bg.wasm differ diff --git a/src/types/mod.rs b/src/types/mod.rs index 2a46d38..14aa106 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,7 +3,7 @@ pub mod units; use std::fmt::Display; /// A quality level in [0, 1] -#[derive(derive_more::Into, PartialEq)] +#[derive(derive_more::Into, PartialEq, Clone, Copy, Debug)] pub struct Quality(f64); impl Quality { diff --git a/src/types/units.rs b/src/types/units.rs index a2cb311..015c9ce 100644 --- a/src/types/units.rs +++ b/src/types/units.rs @@ -1,58 +1,237 @@ -use std::marker::PhantomData; +use std::{ + marker::PhantomData, + ops::{Add, AddAssign, Sub}, +}; -use argmin_math::{ArgminAdd, ArgminMul, ArgminSub}; +use argmin_math::{ArgminAdd, ArgminSub}; +use serde::{Deserialize, Serialize}; + +pub trait Unit: Copy {} // A marker for a value in micrometers. #[derive(Clone, Copy)] pub struct Micrometers; +impl Unit for Micrometers {} + +// A marker for a value in microseconds. +#[derive(Clone, Copy)] +pub struct Microseconds; +impl Unit for Microseconds {} + +// A marker for a value in micrometers. +#[derive(Clone, Copy)] +pub struct Rad; +impl Unit for Rad {} + +#[derive(Clone, Copy)] +pub struct Mul(PhantomData<(T, U)>); +impl Unit for Mul {} + +pub type Square = Mul; +pub type Cube = Mul>; +pub type Pow6 = Mul, Cube>; -/// Coordinates in micrometers. #[derive(Clone, Copy)] -pub struct Coordinates { - pub x: f64, - pub y: f64, +pub struct Inv(PhantomData); +impl Unit for Inv {} + +#[derive(Clone, Copy)] +pub struct Value +where + T: Unit, +{ + value: f64, _phantom: PhantomData, } +impl Value +where + T: Unit, +{ + pub fn new(value: f64) -> Self { + Self { + value, + _phantom: PhantomData, + } + } + pub fn into_inner(self) -> f64 { + self.value + } + pub fn sq(self) -> Value> { + Value { + value: self.value * self.value, + _phantom: PhantomData, + } + } + pub fn cube(self) -> Value>> { + Value { + value: self.value * self.value * self.value, + _phantom: PhantomData, + } + } +} +impl Value> +where + T: Unit, +{ + pub fn sqrt(self) -> Value { + Value { + value: self.value.sqrt(), + _phantom: PhantomData, + } + } +} +impl<'de, T> Deserialize<'de> for Value +where + T: Unit, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::new(f64::deserialize(deserializer)?)) + } +} +impl Serialize for Value +where + T: Unit, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.value.serialize(serializer) + } +} -impl Coordinates { +/// Coordinates in micrometers. +#[derive(Clone, Copy)] +pub struct Coordinates +where + T: Unit, +{ + pub x: Value, + pub y: Value, +} + +impl Coordinates +where + T: Unit, +{ pub fn new(x: f64, y: f64) -> Self { Coordinates { - x, - y, + x: Value::new(x), + y: Value::new(y), + } + } + pub fn sqdist(&self, other: &Coordinates) -> Value> { + (other.x - self.x).sq() + (other.y - self.y).sq() + } +} + +impl ArgminAdd for Value +where + T: Unit, +{ + fn add(&self, other: &Self) -> Self { + Self { + value: self.value + other.value, + _phantom: PhantomData, + } + } +} + +impl ArgminSub for Value +where + T: Unit, +{ + fn sub(&self, other: &Self) -> Self { + Self { + value: self.value - other.value, + _phantom: PhantomData, + } + } +} + +impl Add for Value +where + T: Unit, +{ + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + value: self.value + other.value, + _phantom: PhantomData, + } + } +} + +impl AddAssign for Value +where + T: Unit, +{ + fn add_assign(&mut self, rhs: Self) { + self.value += rhs.value + } +} + +impl std::ops::Mul> for f64 +where + T: Unit, +{ + type Output = Value; + + fn mul(self, rhs: Value) -> Self::Output { + Value { + value: self * rhs.value, _phantom: PhantomData, } } - pub fn sqdist(&self, other: &Coordinates) -> f64 { - (other.x - self.x).powi(2) + (other.y - self.y).powi(2) +} + +impl Sub for Value +where + T: Unit, +{ + type Output = Self; + fn sub(self, other: Self) -> Self { + Self { + value: self.value - other.value, + _phantom: PhantomData, + } } } -impl ArgminAdd for Coordinates { +impl ArgminAdd for Coordinates +where + T: Unit, +{ fn add(&self, other: &Self) -> Self { Self { x: self.x + other.x, y: self.y + other.y, - _phantom: PhantomData, } } } -impl ArgminSub for Coordinates { +impl ArgminSub for Coordinates +where + T: Unit, +{ fn sub(&self, other: &Self) -> Self { Self { x: self.x - other.x, y: self.y - other.y, - _phantom: PhantomData, } } } +/* impl ArgminMul> for Coordinates { fn mul(&self, factor: &f64) -> Self { Self { x: self.x * factor, y: self.y * factor, - _phantom: PhantomData, } } } +*/ diff --git a/tests/data/3sat.yaml b/tests/data/3sat.yaml new file mode 100644 index 0000000..fdd5d2f --- /dev/null +++ b/tests/data/3sat.yaml @@ -0,0 +1,19 @@ +# Data from https://canvas.auckland.ac.nz/courses/14782/files/574983/download?verifier=1xqRikUjTEBwm8PnObD8YVmKdeEhZ9Ui8axW8HwP&wrap=1 +type: max3sat +and: + - or: + - x1 + - x2 + - x3 + - or: + - NOT x1 + - x2 + - x3 + - or: + - x1 + - NOT x2 + - x3 + - or: + - NOT x1 + - x2 + - NOT x3 diff --git a/tests/data/tutorial.yaml b/tests/data/tutorial.yaml index 53352ea..5c10ce9 100644 --- a/tests/data/tutorial.yaml +++ b/tests/data/tutorial.yaml @@ -1,5 +1,5 @@ # Data from https://pulser.readthedocs.io/en/stable/tutorials/qubo.html#Quantum-Adiabatic-Algorithm -version: 0.1.0 +type: qubo data: - -10.0 - 19.7365809 diff --git a/tests/test.rs b/tests/test.rs index 833066e..61746f5 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,9 +1,18 @@ -use qlafoutea::{device::Device, qaa, qubo, types::Quality}; +use std::collections::HashSet; -#[test] -fn test_main() { +use qlafoutea::{ + backend::{ + device::Device, + qaa, + qubo::{self, Constraints}, + }, + runtime::run::run_python, + types::Quality, +}; + +fn qubo_compile() -> String { let half_duration_ns = 4_000; - let constraints = qlafoutea::qubo::Constraints::try_new( + let constraints = Constraints::try_new( 5, vec![ -10.0, @@ -37,14 +46,15 @@ fn test_main() { let device = Device::analog(); - eprintln!("...compiling {} constraints", constraints.len()); - let (register, quality) = constraints + let (register, quality, _) = constraints .layout( &device, &qubo::Options { min_quality: Quality::new(0.1), seed: 75, max_iters: 1_000, + overflow_protection_factor: 0.95, + overflow_protection_threshold: 1_000., }, ) .expect("Failed to compile qubo"); @@ -54,7 +64,7 @@ fn test_main() { quality ); - // Step: integrate QAOA. + // Step: integrate QAA. let sequence = qaa::compile( &constraints, device, @@ -64,6 +74,32 @@ fn test_main() { }, ); - let json = serde_json::to_string_pretty(&sequence).unwrap(); + serde_json::to_string_pretty(&sequence).unwrap() +} + +#[test] +fn test_qubo_compile() { + let json = qubo_compile(); println!("{json}"); } + +#[test] +fn test_qubo_compile_and_run_python() { + let json = qubo_compile(); + let samples = run_python(&json).unwrap(); + + eprintln!("checking samples {:?}", samples); + + // Compare the best two samples against the known-to-be-best-two + // samples, as per https://pulser.readthedocs.io/en/stable/tutorials/qubo.html#Quantum-Adiabatic-Algorithm. + // + // Note that the order is a bit unstable, so the best two could be + // swapped. + let best_two: HashSet<_> = samples + .iter() + .take(2) + .map(|sample| sample.bitstring.as_str()) + .collect(); + let expected_best_two: HashSet<_> = ["00111", "01011"].into_iter().collect(); + assert_eq!(best_two, expected_best_two); +}