diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 631edd75b..7a821f477 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,8 +70,10 @@ jobs: run: cargo test -p ppvm-stim --features rayon # Cross-compile the whole workspace for browser wasm so a wasm regression - # surfaces in CI. `ppvm-python-native` is a CPython extension (never a wasm - # target) and is excluded. Native-only acceleration deps (gxhash, dashmap → + # surfaces in CI. `ppvm-python-native` (a CPython extension) and `ppvm-cli` + # (a terminal binary using clap/rustyline) are never browser-wasm targets and + # are excluded; the reusable engine lives in the library crates, which stay + # covered. Native-only acceleration deps (gxhash, dashmap → # rayon, ahash) are pruned automatically by the `cfg(not(target_arch = # "wasm32"))` dependency tables, and `rand`'s entropy uses the getrandom # `wasm_js` backend wired in `.cargo/config.toml` — so no extra flags here. @@ -89,7 +91,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build workspace for wasm32-unknown-unknown - run: cargo build --target wasm32-unknown-unknown --workspace --exclude ppvm-python-native + run: cargo build --target wasm32-unknown-unknown --workspace --exclude ppvm-python-native --exclude ppvm-cli python-tests: name: Python tests diff --git a/.gitignore b/.gitignore index 35a40df3b..d965bfe49 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .uv/ *__pycache__* profile.json.gz +debug/ # mkdocs build output and cache ppvm-python/site/ diff --git a/AGENTS.md b/AGENTS.md index 98aa97636..522f8fdab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,9 +24,10 @@ ion add QuEraComputing/ppvm/skills/ppvm-usage The skill (`skills/ppvm-usage/SKILL.md` in this repo) covers the Heisenberg / Schrödinger gate-order trap, `Config`-generic `PauliSum` usage, truncation -strategies, and Python / Rust call sites for both backends. Read it before -the Developer Guide if your task is *using* ppvm rather than modifying its -internals. +strategies, Python / Rust call sites for both backends, and the `.sst` +textual program format run by `ppvm-cli` (backend selection, multi-term +observables, `gate trace` / `gate truncate`). Read it before the Developer +Guide if your task is *using* ppvm rather than modifying its internals. ## TL;DR for agents diff --git a/Cargo.lock b/Cargo.lock index f0aaf37d1..7c38456d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -45,17 +45,61 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -68,9 +112,9 @@ dependencies = [ [[package]] name = "ar_archive_writer" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" dependencies = [ "object", ] @@ -81,15 +125,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f5e3dca4e09a6f340a61a0e9c7b61e030c69fc27bf29d73218f7e5e3b7638f" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", "yansi", ] [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bincode" @@ -128,15 +172,21 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.1" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -156,9 +206,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.7.2" +version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2529c31017402be841eb45892278a6c21a000c0a17643af326c73a73f83f0fb" +checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" dependencies = [ "bon-macros", "rustversion", @@ -166,9 +216,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.7.2" +version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82020dadcb845a345591863adb65d74fa8dc5c18a0b6d408470e13b7adc7005" +checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" dependencies = [ "darling", "ident_case", @@ -181,9 +231,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" @@ -205,9 +255,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.61" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -215,9 +265,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" @@ -227,7 +283,21 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures", - "rand_core 0.10.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chumsky" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14377e276b2c8300513dff55ba4cc4142b44e5d6de6d00eb5b2307d650bb4ec1" +dependencies = [ + "hashbrown 0.15.5", + "regex-automata 0.3.9", + "serde", + "stacker", + "unicode-ident", + "unicode-segmentation", ] [[package]] @@ -273,39 +343,107 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583f52b0658b321b25fd6b209b6c76cf058f433071297de64e5980c3d9aad937" +dependencies = [ + "codespan-reporting", + "serde", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", ] [[package]] @@ -418,9 +556,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -428,11 +566,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -442,9 +579,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", @@ -453,9 +590,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -466,11 +603,43 @@ dependencies = [ "rayon", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encode_unicode" @@ -478,6 +647,35 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "endian-type" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -491,7 +689,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", ] [[package]] @@ -524,6 +738,30 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -535,29 +773,27 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasi", + "r-efi 5.3.0", + "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "rand_core 0.10.0", - "wasip2", - "wasip3", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] @@ -572,12 +808,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -599,9 +836,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -610,10 +847,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "id-arena" -version = "2.3.0" +name = "home" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys", +] [[package]] name = "ident_case" @@ -621,39 +861,50 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" -version = "2.11.4" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.0", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] name = "insta" -version = "1.43.2" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" dependencies = [ "console", "once_cell", "serde", "similar", + "tempfile", ] [[package]] name = "inventory" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -674,31 +925,51 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "js-sys" -version = "0.3.81" +name = "jiff" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" dependencies = [ - "once_cell", - "wasm-bindgen", + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", ] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "jiff-static" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] [[package]] name = "libc" -version = "0.2.176" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libmimalloc-sys" @@ -711,31 +982,30 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mimalloc" @@ -746,6 +1016,27 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num" version = "0.4.3" @@ -830,9 +1121,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -852,15 +1149,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -869,6 +1166,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "plotters" version = "0.3.7" @@ -899,9 +1202,18 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] [[package]] name = "ppv-lite86" @@ -923,6 +1235,16 @@ dependencies = [ "ppvm-tableau", ] +[[package]] +name = "ppvm-cli" +version = "0.1.0" +dependencies = [ + "clap", + "eyre", + "ppvm-vihaco", + "rustyline", +] + [[package]] name = "ppvm-pauli-sum" version = "0.1.0" @@ -1018,11 +1340,12 @@ dependencies = [ "bnum", "criterion 0.7.0", "fxhash", - "getrandom 0.4.1", + "getrandom 0.4.3", "insta", "itertools 0.14.0", "num", "ppvm-pauli-sum", + "ppvm-pauli-word", "ppvm-traits", "rand 0.10.1", "rayon", @@ -1064,6 +1387,28 @@ dependencies = [ "rayon", ] +[[package]] +name = "ppvm-vihaco" +version = "0.1.0" +dependencies = [ + "bitvec", + "bnum", + "chumsky 0.10.1", + "eyre", + "log", + "num", + "ppvm-pauli-sum", + "ppvm-tableau", + "ppvm-traits", + "rayon", + "smallvec", + "vihaco", + "vihaco-circuit-isa", + "vihaco-cpu", + "vihaco-parser", + "vihaco-parser-core", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1074,11 +1419,33 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1091,12 +1458,12 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.13.0", "num-traits", "rand 0.9.4", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", "rusty-fork", "tempfile", "unarray", @@ -1178,9 +1545,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.41" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -1191,12 +1558,28 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.9.4" @@ -1214,8 +1597,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.1", - "rand_core 0.10.0", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1234,14 +1617,14 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xorshift" @@ -1254,9 +1637,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -1274,23 +1657,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.11", - "regex-syntax 0.8.6", + "regex-automata 0.4.14", + "regex-syntax 0.8.11", ] [[package]] @@ -1306,13 +1689,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", ] [[package]] @@ -1323,21 +1706,21 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1359,10 +1742,25 @@ dependencies = [ ] [[package]] -name = "ryu" -version = "1.0.20" +name = "rustyline" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "clipboard-win", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width 0.2.2", + "utf8parse", + "windows-sys", +] [[package]] name = "same-file" @@ -1379,12 +1777,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -1417,22 +1809,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "similar" @@ -1440,11 +1832,17 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "stacker" @@ -1456,7 +1854,7 @@ dependencies = [ "cfg-if", "libc", "psm", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1464,7 +1862,7 @@ name = "stim-parser" version = "0.1.0" dependencies = [ "ariadne", - "chumsky", + "chumsky 0.12.0", "proptest", ] @@ -1476,9 +1874,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -1493,21 +1891,30 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", ] [[package]] @@ -1548,15 +1955,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -1565,10 +1972,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-width" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unty" @@ -1576,12 +1983,98 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vihaco" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "690c9db1827ed6e392340d3d9c65408213c7bb4c030b5e8991b03459e5c7bb17" +dependencies = [ + "byteorder", + "chumsky 0.10.1", + "codespan", + "colored", + "env_logger", + "eyre", + "log", + "smallvec", + "vihaco-derive", + "vihaco-parser", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-circuit-isa" +version = "0.1.0" +dependencies = [ + "chumsky 0.10.1", + "eyre", + "smallvec", + "vihaco", + "vihaco-parser", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-cpu" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be6cffffe3fca901bcddc389422dd9d2f7041038571a758a03c63e53ce1b6d5e" +dependencies = [ + "chumsky 0.10.1", + "codespan", + "eyre", + "log", + "vihaco", + "vihaco-derive", + "vihaco-parser", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f11ac1123518d4bec265847a1a12e406c1b54f4ef2478db87f4e6986e26b918" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "vihaco-parser" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b0c3b54b4caeaaeeda2953fa273155887e89ebdb0ce80b65d23c435b1e70a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-parser-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea97e3bc67e4b5a97e21f2a4f64e4a87749038850968021ec903cc595353d20" +dependencies = [ + "chumsky 0.10.1", +] + [[package]] name = "virtue" version = "0.0.18" @@ -1607,38 +2100,20 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen 0.46.0", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -1647,25 +2122,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1673,65 +2134,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" dependencies = [ "js-sys", "wasm-bindgen", @@ -1759,7 +2186,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1774,15 +2201,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1792,163 +2210,11 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "wyz" @@ -1967,20 +2233,26 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 18abb3c44..61dfd384c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,14 @@ members = [ "crates/ppvm-sym", "crates/ppvm-python-native", "crates/ppvm-tableau", - "crates/ppvm-stim", "crates/stim-parser", + "crates/ppvm-stim", + "crates/stim-parser", + "crates/ppvm-vihaco", "crates/ppvm-tableau-sum", # Runnable copies of the Rust code blocks in skills/ppvm-usage/SKILL.md. # Built by `cargo build --workspace --all-targets` in CI so the skill # can't silently drift away from the public API. - "skills/ppvm-usage/examples/rust", + "skills/ppvm-usage/examples/rust", "crates/ppvm-cli", "crates/vihaco-circuit-isa", ] # Keep function names and line numbers in release builds so profilers diff --git a/crates/ppvm-cli/Cargo.toml b/crates/ppvm-cli/Cargo.toml new file mode 100644 index 000000000..4ff2b6937 --- /dev/null +++ b/crates/ppvm-cli/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ppvm-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.1", features = ["derive"] } +eyre = "0.6.12" +ppvm-vihaco = { version = "0.1.0", path = "../ppvm-vihaco", features = ["rayon"] } +rustyline = "18.0.0" + +[[bin]] +name = "ppvm" +path = "src/main.rs" diff --git a/crates/ppvm-cli/README.md b/crates/ppvm-cli/README.md new file mode 100644 index 000000000..5a9ff2249 --- /dev/null +++ b/crates/ppvm-cli/README.md @@ -0,0 +1,137 @@ +# ppvm-cli + +Command-line front-end for the Pauli-propagation virtual machine. Parses, +dumps, runs, and steps through `.sst` programs (and their compiled `.ssb` +bytecode). + +## Install + +From this crate directory: + +```sh +cargo install --path crates/ppvm-cli +``` + +This builds the release binary and copies it to `~/.cargo/bin/ppvm`. As long as +`~/.cargo/bin` is on your `PATH`, you can then invoke `ppvm` from anywhere. + +During development you can skip the install and use `cargo run` instead — just +put CLI arguments after `--`: + +```sh +cargo run -p ppvm-cli -- run examples/ghz.sst +``` + +## Run + +`run` executes a program for one or more shots and prints the measurement +results. The example [`examples/ghz.sst`](examples/ghz.sst) prepares a 3-qubit +GHZ state and measures every qubit, so each shot reads `000` or `111`: + +```sh +$ ppvm run examples/ghz.sst +000 +``` + +Each shot is printed as a single flat bit string — `0`/`1`, with a lost qubit +shown as `2` — and shots are separated by newlines. Use `-s`/`--shots` to run +more than one: + +```sh +$ ppvm run examples/ghz.sst --shots 5 +000 +000 +111 +000 +111 +``` + +Other options: + +- `-t`/`--threads ` — run shots across `N` threads. More than one enables + parallel execution (defaults to 1). +- `--seed ` — seed the RNG for reproducible results. The same seed yields the + same shots regardless of the thread count. +- `-o`/`--output ` — write the results to a file (one shot per line) + instead of stdout. +- `-f debug` — print the raw record for every shot instead of bit strings. +- `-q`/`--quiet` — run without printing anything. + +```sh +$ ppvm run examples/ghz.sst --shots 2 -f debug +[[[Zero], [Zero], [Zero]], [[One], [One], [One]]] + +$ ppvm run examples/ghz.sst --shots 1000 --threads 8 -o results.txt +Results written to results.txt +``` + +## Dump + +`dump` compiles a `.sst` program to `.ssb` bytecode. With no `-o`, it writes +next to the input (`ghz.sst` → `ghz.ssb`): + +```sh +$ ppvm dump examples/ghz.sst +Bytecode written to examples/ghz.ssb +``` + +`run` auto-detects the format from the file's contents, so the bytecode runs the +same way as the source: + +```sh +$ ppvm run examples/ghz.ssb +000 +``` + +`dump` refuses to overwrite an existing file unless you pass `-f`/`--force`. + +## Debug + +`debug` steps through a program interactively. At each pause it prints the +program counter, the next instruction, and the measurements so far, then waits +for a command (type the letter and press Enter; a bare Enter steps): + +- `s` — step one instruction +- `c` — continue to the next breakpoint (or the end) +- `q` — quit + +By default it pauses at `breakpoint` instructions in the program. Add one +wherever you want execution to stop: + +``` +fn @main() { + const.u64 0 + circuit h + + // execution pauses here + breakpoint + + const.u64 0 + circuit measure + ret +} +``` + +```sh +$ printf 's\nc\n' | ppvm debug program.sst +-- breakpoint hit -- +pc=3 next: const.u64 0 +measurements: +> s step | c continue | q quit: pc=4 next: Measure +measurements: +> s step | c continue | q quit: Program finished. +Measurements: 0 +``` + +To step through a program that has no breakpoints, pass `-b`/`--break-at-start` +to pause before the very first instruction: + +```sh +$ ppvm debug examples/ghz.sst -b +pc=0 next: const.u64 0 +measurements: +> s step | c continue | q quit: +``` + +Batch `run` ignores `breakpoint` instructions entirely, so the same file still +runs straight through with `ppvm run`. diff --git a/crates/ppvm-cli/examples/bit_flip_correction.sst b/crates/ppvm-cli/examples/bit_flip_correction.sst new file mode 100644 index 000000000..d888b80ae --- /dev/null +++ b/crates/ppvm-cli/examples/bit_flip_correction.sst @@ -0,0 +1,66 @@ +device circuit.n_qubits 5; + +// Data: q0, q1, q2. +// Syndrome ancillas: q3, q4. +fn @main() { + const.u64 1 + const.f64 0.25 + const.f64 0.0 + const.f64 0.0 + circuit paulierror + + const.u64 0 + const.u64 3 + circuit cnot + const.u64 1 + const.u64 3 + circuit cnot + const.u64 3 + circuit measure + + const.u64 1 + const.u64 4 + circuit cnot + const.u64 2 + const.u64 4 + circuit cnot + const.u64 4 + circuit measure + + const.u32 1 + eq.u32 + cond_br @s12_one, @s12_zero + +@s12_one: + const.u32 1 + eq.u32 + cond_br @correct_q1, @correct_q2 + +@s12_zero: + const.u32 1 + eq.u32 + cond_br @correct_q0, @readout + +@correct_q0: + const.u64 0 + circuit x + br @readout + +@correct_q1: + const.u64 1 + circuit x + br @readout + +@correct_q2: + const.u64 2 + circuit x + +@readout: + const.u64 0 + circuit measure + const.u64 1 + circuit measure + const.u64 2 + circuit measure + ret +} diff --git a/crates/ppvm-cli/examples/ghz.sst b/crates/ppvm-cli/examples/ghz.sst new file mode 100644 index 000000000..9eb42fb26 --- /dev/null +++ b/crates/ppvm-cli/examples/ghz.sst @@ -0,0 +1,27 @@ +device circuit.n_qubits 3; + +// Prepare a 3-qubit GHZ state (|000> + |111>)/sqrt(2) and measure every qubit. +// The three outcomes are perfectly correlated, so each shot reads 0 0 0 or 1 1 1. +fn @main() { + const.u64 0 + circuit h + + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 1 + const.u64 2 + circuit cnot + + const.u64 0 + circuit measure + + const.u64 1 + circuit measure + + const.u64 2 + circuit measure + + ret +} diff --git a/crates/ppvm-cli/examples/heisenberg_zz.sst b/crates/ppvm-cli/examples/heisenberg_zz.sst new file mode 100644 index 000000000..2465e64f4 --- /dev/null +++ b/crates/ppvm-cli/examples/heisenberg_zz.sst @@ -0,0 +1,17 @@ +device circuit.n_qubits 2; +device circuit.backend paulisum; +device circuit.observable 1.0*ZZ+0.5*XX; +device circuit.coefficient_threshold 1e-10; + +// Heisenberg-picture run on a PauliSum target. The header observable +// `1.0*ZZ + 0.5*XX` seeds two terms; tracing `[XZ]?*` filters both ZZ +// (coef 1.0) and XX (coef 0.5), so the trace is their coefficient sum. +// +// No gates here, so the trace returns the seeded observable's coefficient +// sum directly: 1.0 + 0.5 = 1.5. Add `circuit cnot; circuit h; circuit truncate` +// in textbook-reversed order to evolve before tracing. +fn @main() { + const.str "[XZ]?*" + circuit trace + ret +} diff --git a/crates/ppvm-cli/examples/loop_feedforward.pseudo b/crates/ppvm-cli/examples/loop_feedforward.pseudo new file mode 100644 index 000000000..eeec2fce3 --- /dev/null +++ b/crates/ppvm-cli/examples/loop_feedforward.pseudo @@ -0,0 +1,16 @@ + def main(): + measurement_record = [] + + for _ in range(3): + circuit.h(q0) + + q0_outcome = circuit.measure(q0) + measurement_record.append(q0_outcome) + + if q0_outcome == 1: + circuit.x(q1) + + q1_outcome = circuit.measure(q1) + measurement_record.append(q1_outcome) + + return measurement_record diff --git a/crates/ppvm-cli/examples/loop_feedforward.sst b/crates/ppvm-cli/examples/loop_feedforward.sst new file mode 100644 index 000000000..ab7676310 --- /dev/null +++ b/crates/ppvm-cli/examples/loop_feedforward.sst @@ -0,0 +1,44 @@ +device circuit.n_qubits 2; + +// Loop + feed-forward demo. +// Repeat a body 3 times (a counted loop); in each round flip a fair coin on +// q0 and, based on the measured outcome, conditionally toggle q1 (the +// feed-forward). q1 is flipped once per coin that came up 1, so the final q1 +// measurement equals the parity (XOR) of the three coin flips. Each shot +// prints 4 bits: q0_round1 q0_round2 q0_round3 q1. +fn @main() { + // Loop counter starts at 0 and lives on the bottom of the stack. + const.u64 0 + +@loop: + // Flip a fair coin: H then measure q0 (pushes outcome 0/1 as u32). + const.u64 0 + circuit h + const.u64 0 + circuit measure + + // Feed-forward: if the outcome was 1, apply X to q1. + const.u32 1 + eq.u32 + cond_br @flip, @next + +@flip: + const.u64 1 + circuit x + br @next + +@next: + // counter += 1, then loop while counter < 3. + const.u64 1 + add.u64 + dup + const.u64 3 + lt.u64 + cond_br @loop, @done + +@done: + // Final readout of q1. + const.u64 1 + circuit measure + ret +} diff --git a/crates/ppvm-cli/examples/simple_loop.sst b/crates/ppvm-cli/examples/simple_loop.sst new file mode 100644 index 000000000..a52a8ac1f --- /dev/null +++ b/crates/ppvm-cli/examples/simple_loop.sst @@ -0,0 +1,33 @@ +device circuit.n_qubits 1; + +fn @main() { + const.u64 0 + +@loop: + const.u64 0 + circuit h + const.u64 0 + circuit measure + + // stop here to investigate + breakpoint + + const.u32 1 + eq.u32 + cond_br @flip, @next + +@flip: + const.u64 0 + circuit x + +@next: + const.u64 1 + add.u64 + dup + const.u64 2 + lt.u64 + cond_br @loop, @done + +@done: + ret +} diff --git a/crates/ppvm-cli/src/commands.rs b/crates/ppvm-cli/src/commands.rs new file mode 100644 index 000000000..b6d3db799 --- /dev/null +++ b/crates/ppvm-cli/src/commands.rs @@ -0,0 +1,580 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use eyre::{Result, WrapErr}; +use ppvm_vihaco::composite::{PPVM, StepOutcome}; +use ppvm_vihaco::measurements::MeasurementResult; +use ppvm_vihaco::shots::ShotRecord; +use std::io::{BufRead, Write}; +use std::path::Path; + +/// Output format for `parse`. +#[derive(Clone, Debug, clap::ValueEnum)] +pub enum Format { + Pretty, + Debug, + Json, +} + +/// Output format for the measurement record from `run`. +#[derive(Clone, Debug, clap::ValueEnum)] +pub enum MeasurementFormat { + /// One flat bit string per shot: `0`/`1`, lost qubit = `2`. + Bits, + /// Raw debug representation of all shots. + Debug, +} + +pub fn run( + file: &str, + shots: usize, + seed: Option, + output: Option<&str>, + quiet: bool, + format: MeasurementFormat, +) -> Result<()> { + // Compile once, then run every shot against the shared module. The thread + // pool is sized once in `main` via the top-level `--threads` flag. + let module = + ppvm_vihaco::load_module_file(file).wrap_err_with(|| format!("failed to load {file}"))?; + let records = ppvm_vihaco::shots::run_shots(&module, shots, seed) + .wrap_err_with(|| format!("failed to run {file}"))?; + if quiet { + return Ok(()); + } + + let text = match format { + MeasurementFormat::Bits => format_shot_records(&records), + MeasurementFormat::Debug => format!("{records:?}"), + }; + + match output { + Some(path) => { + std::fs::write(path, format!("{text}\n")) + .wrap_err_with(|| format!("failed to write {path}"))?; + eprintln!("Results written to {path}"); + } + None => println!("{text}"), + } + Ok(()) +} + +/// Render one shot per line. +fn format_shot_records(records: &[ShotRecord]) -> String { + records + .iter() + .map(format_shot_record) + .collect::>() + .join("\n") +} + +/// Render one shot. Layout: +/// - Tableau-style (only measurements): `bits` — same as the pre-trace format. +/// - PauliSum-style (only traces): comma-separated floats. +/// - Both present: `bits | t0,t1,...`. +/// - Both empty: empty string. +fn format_shot_record(record: &ShotRecord) -> String { + let bits = format_shot(&record.measurements); + let traces = format_traces(&record.traces); + match (bits.is_empty(), traces.is_empty()) { + (false, false) => format!("{bits} | {traces}"), + (false, true) => bits, + (true, false) => traces, + (true, true) => String::new(), + } +} + +/// Render a shot's full measurement record as one flat bit string, all events +/// and qubits concatenated: `Zero` → `0`, `One` → `1`, `Lost` → `2` (the +/// outcome's own enum value). An empty record renders as the empty string. +fn format_shot(record: &[MeasurementResult]) -> String { + record + .iter() + .flatten() + .map(|outcome| char::from(b'0' + *outcome as u8)) + .collect() +} + +/// Render trace values as comma-separated floats with default `f64` formatting +/// (`1.0` → `"1"`, `1.5` → `"1.5"`, etc.). Empty record renders as empty string. +fn format_traces(traces: &[f64]) -> String { + traces + .iter() + .map(|t| format!("{t}")) + .collect::>() + .join(",") +} + +pub fn parse(file: &str, format: Format) -> Result<()> { + let source = + std::fs::read_to_string(file).wrap_err_with(|| format!("failed to read {file}"))?; + let parsed = ppvm_vihaco::parse_program(&source)?; + + match format { + Format::Json => { + eprintln!("Warning: JSON format not yet supported for AST, using debug format"); + println!("{:#?}", parsed); + } + Format::Debug => { + println!("{:#?}", parsed); + } + Format::Pretty => { + println!("Module:"); + println!(" Headers: {}", parsed.headers.len()); + for (i, header) in parsed.headers.iter().enumerate() { + println!(" [{}] {:?}", i, header); + } + println!(" Functions: {}", parsed.functions.len()); + for (i, func) in parsed.functions.iter().enumerate() { + println!( + " [{}] {}({} params, {} body items)", + i, + func.name, + func.params.len(), + func.body.len() + ); + } + } + } + + Ok(()) +} + +pub fn dump(file: &str, output: Option<&str>, force: bool) -> Result<()> { + let output_file = match output { + Some(output_file_name) => output_file_name.to_string(), + None => Path::new(file) + .with_extension("ssb") + .to_string_lossy() + .into_owned(), + }; + + // Don't clobber an existing file unless asked to. + if !force && Path::new(&output_file).exists() { + return Err(eyre::eyre!( + "{output_file} already exists; pass --force to overwrite" + )); + } + + ppvm_vihaco::dump_file(file, &output_file) + .wrap_err_with(|| format!("failed to dump {file}"))?; + eprintln!("Bytecode written to {output_file}"); + Ok(()) +} + +/// A command entered at the debugger prompt. +enum DebugCommand { + Step, + Continue, + Quit, +} + +/// Step through a program interactively, pausing at `breakpoint` instructions. +/// With `break_at_start`, also pauses before the first instruction so any +/// program can be stepped from the beginning. +pub fn debug(file: &str, break_at_start: bool) -> Result<()> { + let stdin = std::io::stdin(); + let mut input = stdin.lock(); + let mut output = std::io::stdout(); + debug_loop(file, break_at_start, &mut input, &mut output) +} + +/// Core debugger loop, generic over its IO so it can be driven by tests. +fn debug_loop( + file: &str, + break_at_start: bool, + input: &mut impl BufRead, + output: &mut impl Write, +) -> Result<()> { + let mut machine = PPVM::default(); + machine + .load_file(file) + .wrap_err_with(|| format!("failed to load {file}"))?; + machine.init()?; + + let mut paused = break_at_start; + let mut ever_paused = paused; + + loop { + // Safety net: stop if execution has run off the end of the code. + if machine.current_instruction().is_none() { + writeln!(output, "Program counter past end of code.")?; + break; + } + + if paused { + print_location(&machine, output)?; + match read_command(input, output)? { + DebugCommand::Quit => { + writeln!(output, "Quit.")?; + return Ok(()); + } + DebugCommand::Continue => paused = false, + DebugCommand::Step => {} + } + } + + match machine.step_once()? { + StepOutcome::Continue => {} + StepOutcome::Breakpoint => { + paused = true; + ever_paused = true; + writeln!(output, "-- breakpoint hit --")?; + } + StepOutcome::Return | StepOutcome::Halt => { + writeln!(output, "Program finished.")?; + break; + } + } + } + + writeln!( + output, + "Measurements: {}", + format_shot(&machine.measurement_record()) + )?; + let traces = machine.trace_record(); + if !traces.is_empty() { + writeln!(output, "Traces: {}", format_traces(&traces))?; + } + if !ever_paused { + writeln!( + output, + "(no breakpoint was hit; pass --break-at-start to step from the beginning)" + )?; + } + Ok(()) +} + +/// Print the program counter, the next instruction, and measurements so far. +fn print_location(machine: &PPVM, output: &mut impl Write) -> Result<()> { + let pc = machine.current_pc(); + match machine.current_instruction() { + Some(inst) => writeln!(output, "pc={pc} next: {inst}")?, + None => writeln!(output, "pc={pc} (end of code)")?, + } + writeln!( + output, + "measurements: {}", + format_shot(&machine.measurement_record()) + )?; + let traces = machine.trace_record(); + if !traces.is_empty() { + writeln!(output, "traces: {}", format_traces(&traces))?; + } + Ok(()) +} + +/// Prompt for and read a debugger command. A bare Enter steps; EOF quits. +fn read_command(input: &mut impl BufRead, output: &mut impl Write) -> Result { + loop { + write!(output, "> s step | c continue | q quit: ")?; + output.flush()?; + + let mut line = String::new(); + if input.read_line(&mut line)? == 0 { + // EOF (e.g. stdin closed): treat as quit so we never spin. + return Ok(DebugCommand::Quit); + } + match line.trim() { + "" | "s" | "step" => return Ok(DebugCommand::Step), + "c" | "continue" => return Ok(DebugCommand::Continue), + "q" | "quit" => return Ok(DebugCommand::Quit), + other => writeln!(output, "unknown command: {other:?}")?, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ppvm_vihaco::measurements::MeasurementOutcome; + use std::fs; + + /// Minimal program that compiles and measures q0 in |0> (deterministic). + const PROGRAM: &str = + "device circuit.n_qubits 1;\nfn @main() { const.u64 0\n circuit measure\n ret }\n"; + + fn row(outcomes: &[MeasurementOutcome]) -> MeasurementResult { + outcomes.iter().copied().collect() + } + + /// Write `contents` to a uniquely-named temp file and return its path. + fn temp_file(name: &str, contents: &str) -> String { + let path = std::env::temp_dir().join(name); + fs::write(&path, contents).unwrap(); + path.to_string_lossy().into_owned() + } + + // ─── format_shot ───────────────────────────────────────────────────── + + #[test] + fn format_shot_empty_record_is_empty() { + assert_eq!(format_shot(&[]), ""); + } + + #[test] + fn format_shot_concatenates_qubits_within_an_event() { + let record = vec![row(&[ + MeasurementOutcome::One, + MeasurementOutcome::Zero, + MeasurementOutcome::One, + ])]; + assert_eq!(format_shot(&record), "101"); + } + + #[test] + fn format_shot_flattens_events_with_no_separator() { + let record = vec![ + row(&[MeasurementOutcome::One]), + row(&[MeasurementOutcome::Zero]), + ]; + assert_eq!(format_shot(&record), "10"); + } + + #[test] + fn format_shot_renders_lost_qubit_as_two() { + let record = vec![ + row(&[MeasurementOutcome::One, MeasurementOutcome::Lost]), + row(&[MeasurementOutcome::Zero]), + ]; + assert_eq!(format_shot(&record), "120"); + } + + #[test] + fn format_shot_records_joins_shots_with_newlines() { + let shots = vec![ + ShotRecord { + measurements: vec![row(&[MeasurementOutcome::One])], + traces: vec![], + }, + ShotRecord { + measurements: vec![row(&[MeasurementOutcome::Zero])], + traces: vec![], + }, + ]; + assert_eq!(format_shot_records(&shots), "1\n0"); + } + + #[test] + fn format_shot_record_traces_only_shows_floats() { + let shot = ShotRecord { + measurements: vec![], + traces: vec![1.0, 0.5], + }; + assert_eq!(format_shot_record(&shot), "1,0.5"); + } + + #[test] + fn format_shot_record_both_present_joins_with_pipe() { + let shot = ShotRecord { + measurements: vec![row(&[MeasurementOutcome::One])], + traces: vec![0.25], + }; + assert_eq!(format_shot_record(&shot), "1 | 0.25"); + } + + #[test] + fn format_shot_record_both_empty_is_empty_string() { + assert_eq!(format_shot_record(&ShotRecord::default()), ""); + } + + // ─── run ─────────────────────────────────────────────────────────── + + #[test] + fn run_succeeds_on_valid_file() { + let src = temp_file("ppvm_cli_run_ok.sst", PROGRAM); + let res = run(&src, 3, None, None, true, MeasurementFormat::Bits); + let _ = fs::remove_file(&src); + assert!(res.is_ok(), "got: {res:?}"); + } + + #[test] + fn run_writes_one_line_per_shot_to_output_file() { + let src = temp_file("ppvm_cli_run_output.sst", PROGRAM); + let out = std::env::temp_dir().join("ppvm_cli_run_output.txt"); + let _ = fs::remove_file(&out); + + run(&src, 4, None, out.to_str(), false, MeasurementFormat::Bits).unwrap(); + let contents = fs::read_to_string(&out).unwrap(); + // Four deterministic shots of |0>, one per line. + assert_eq!(contents, "0\n0\n0\n0\n"); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&out); + } + + #[test] + fn run_outputs_trace_record_for_paulisum_backend() { + // Single-qubit PauliSum with observable Z, no gates, trace `Z?*` → 1.0. + // The .sst-driven path should surface the trace value in `run`'s output. + const TRACE_PROGRAM: &str = "device circuit.n_qubits 1;\n\ + device circuit.backend paulisum;\n\ + device circuit.observable Z;\n\ + fn @main() { const.str \"Z?*\"\n circuit trace\n ret }\n"; + let src = temp_file("ppvm_cli_run_trace.sst", TRACE_PROGRAM); + let out = std::env::temp_dir().join("ppvm_cli_run_trace.txt"); + let _ = fs::remove_file(&out); + + run(&src, 1, None, out.to_str(), false, MeasurementFormat::Bits).unwrap(); + let contents = fs::read_to_string(&out).unwrap(); + assert_eq!(contents, "1\n", "expected trace value 1.0 in output"); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&out); + } + + #[test] + fn run_errors_with_context_on_missing_file() { + let err = run( + "/no/such/file.sst", + 1, + None, + None, + false, + MeasurementFormat::Bits, + ) + .unwrap_err(); + assert!(err.to_string().contains("failed to load"), "got: {err}"); + } + + // ─── parse ───────────────────────────────────────────────────────── + + #[test] + fn parse_succeeds_on_valid_file() { + let src = temp_file("ppvm_cli_parse_ok.sst", PROGRAM); + let res = parse(&src, Format::Debug); + let _ = fs::remove_file(&src); + assert!(res.is_ok(), "got: {res:?}"); + } + + #[test] + fn parse_errors_with_context_on_missing_file() { + let err = parse("/no/such/file.sst", Format::Pretty).unwrap_err(); + assert!(err.to_string().contains("failed to read"), "got: {err}"); + } + + // ─── dump ────────────────────────────────────────────────────────── + + #[test] + fn dump_writes_default_ssb_path_when_output_omitted() { + let src = temp_file("ppvm_cli_dump_default.sst", PROGRAM); + let expected = Path::new(&src).with_extension("ssb"); + let _ = fs::remove_file(&expected); // clear any leftover from a prior run + + dump(&src, None, false).unwrap(); + assert!(expected.exists(), "default .ssb should have been written"); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&expected); + } + + #[test] + fn dump_writes_explicit_output_path() { + let src = temp_file("ppvm_cli_dump_explicit.sst", PROGRAM); + let out = std::env::temp_dir().join("ppvm_cli_dump_explicit_out.ssb"); + let _ = fs::remove_file(&out); + + dump(&src, Some(out.to_str().unwrap()), false).unwrap(); + assert!(out.exists()); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&out); + } + + #[test] + fn dump_refuses_to_clobber_without_force() { + let src = temp_file("ppvm_cli_dump_clobber.sst", PROGRAM); + let out = std::env::temp_dir().join("ppvm_cli_dump_clobber_out.ssb"); + fs::write(&out, b"existing").unwrap(); + + let err = dump(&src, Some(out.to_str().unwrap()), false).unwrap_err(); + assert!(err.to_string().contains("already exists"), "got: {err}"); + // The existing file must be left untouched. + assert_eq!(fs::read(&out).unwrap(), b"existing"); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&out); + } + + #[test] + fn dump_overwrites_existing_with_force() { + let src = temp_file("ppvm_cli_dump_force.sst", PROGRAM); + let out = std::env::temp_dir().join("ppvm_cli_dump_force_out.ssb"); + fs::write(&out, b"existing").unwrap(); + + dump(&src, Some(out.to_str().unwrap()), true).unwrap(); + // Replaced with real bytecode, not the placeholder. + assert_ne!(fs::read(&out).unwrap(), b"existing"); + + let _ = fs::remove_file(&src); + let _ = fs::remove_file(&out); + } + + // ─── debug ───────────────────────────────────────────────────────── + + /// Program with a `breakpoint` before measuring q0 in |0> (deterministic). + const BREAKPOINT_PROGRAM: &str = "device circuit.n_qubits 1;\nfn @main() { breakpoint\n const.u64 0\n circuit measure\n ret }\n"; + + /// Drive `debug_loop` with scripted input, returning the captured output. + fn run_debug(program: &str, name: &str, break_at_start: bool, script: &str) -> String { + let src = temp_file(name, program); + let mut input = script.as_bytes(); + let mut output: Vec = Vec::new(); + debug_loop(&src, break_at_start, &mut input, &mut output).unwrap(); + let _ = fs::remove_file(&src); + String::from_utf8(output).unwrap() + } + + #[test] + fn debug_break_at_start_steps_through_to_finish() { + // PROGRAM is const.u64 0 / circuit measure / ret = 3 steps. + let out = run_debug(PROGRAM, "ppvm_cli_debug_step.sst", true, "s\ns\ns\n"); + assert!( + out.contains("next: Measure"), + "should display the gate: {out}" + ); + assert!(out.contains("Program finished."), "{out}"); + assert!( + out.contains("Measurements: 0"), + "q0 in |0> measures 0: {out}" + ); + } + + #[test] + fn debug_continue_runs_to_end() { + let out = run_debug(PROGRAM, "ppvm_cli_debug_continue.sst", true, "c\n"); + assert!(out.contains("Program finished."), "{out}"); + assert!(out.contains("Measurements: 0"), "{out}"); + } + + #[test] + fn debug_honors_authored_breakpoint() { + // Not breaking at start: must run until the `breakpoint` pauses it. + let out = run_debug(BREAKPOINT_PROGRAM, "ppvm_cli_debug_bp.sst", false, "c\n"); + assert!(out.contains("-- breakpoint hit --"), "{out}"); + assert!(out.contains("Program finished."), "{out}"); + // A breakpoint was hit, so no "use --break-at-start" hint. + assert!(!out.contains("no breakpoint was hit"), "{out}"); + } + + #[test] + fn debug_quit_stops_before_finishing() { + let out = run_debug(PROGRAM, "ppvm_cli_debug_quit.sst", true, "q\n"); + assert!(out.contains("Quit."), "{out}"); + assert!(!out.contains("Program finished."), "{out}"); + assert!( + !out.contains("Measurements:"), + "quit prints no record: {out}" + ); + } + + #[test] + fn debug_without_breakpoint_prints_hint() { + // No breakpoint, no break-at-start, empty input: runs straight through + // and tells the user how to step. + let out = run_debug(PROGRAM, "ppvm_cli_debug_hint.sst", false, ""); + assert!(out.contains("Program finished."), "{out}"); + assert!(out.contains("no breakpoint was hit"), "{out}"); + } +} diff --git a/crates/ppvm-cli/src/main.rs b/crates/ppvm-cli/src/main.rs new file mode 100644 index 000000000..c9cb5a007 --- /dev/null +++ b/crates/ppvm-cli/src/main.rs @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Parser, Subcommand}; +use eyre::Result; + +mod commands; +mod repl; + +#[derive(Parser)] +#[command(name = "ppvm")] +#[command(about = "Pauli propagation virtual machine", long_about = None)] +pub struct Cli { + /// Number of threads for all parallel work (1 = fully serial & deterministic) + #[arg(short, long, default_value = "1")] + threads: usize, + + /// Subcommand to run; with none, drops into the interactive REPL. + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands { + /// Parse a .sst file and output the AST + Parse { + /// Input .sst file + #[arg(value_name = "FILE")] + file: String, + + /// Output format + #[arg(short, long, value_enum, default_value = "pretty")] + format: commands::Format, + }, + /// Compile a .sst file to bytecode + Dump { + /// Input .sst file + #[arg(value_name = "FILE")] + file: String, + + /// Output file (optional, defaults to .ssb) + #[arg(short, long)] + output: Option, + + /// Overwrite the output file if it already exists + #[arg(short, long)] + force: bool, + }, + + /// Run a .sst or .ssb program + Run { + /// Input file (.sst source or .ssb bytecode) + #[arg(value_name = "FILE")] + file: String, + + /// Number of shots to run + #[arg(short, long, default_value = "1")] + shots: usize, + + /// Seed the RNG for reproducible results + #[arg(long)] + seed: Option, + + /// Write results to a file instead of stdout + #[arg(short, long)] + output: Option, + + /// Suppress the measurement record + #[arg(short, long)] + quiet: bool, + + /// Measurement output format + #[arg(short, long, value_enum, default_value = "bits")] + format: commands::MeasurementFormat, + }, + + /// Step through a program interactively, pausing at `breakpoint` instructions + Debug { + /// Input file (.sst source or .ssb bytecode) + #[arg(value_name = "FILE")] + file: String, + + /// Pause before the first instruction so any program can be stepped + #[arg(short, long)] + break_at_start: bool, + }, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + // Size the process-wide thread pool once; governs all parallelism (across + // shots and within a single machine). `--threads 1` is fully serial. + ppvm_vihaco::shots::set_global_threads(cli.threads)?; + + match cli.command { + None => repl::repl()?, + Some(Commands::Parse { file, format }) => { + commands::parse(&file, format)?; + } + Some(Commands::Dump { + file, + output, + force, + }) => { + commands::dump(&file, output.as_deref(), force)?; + } + Some(Commands::Run { + file, + shots, + seed, + output, + quiet, + format, + }) => { + commands::run(&file, shots, seed, output.as_deref(), quiet, format)?; + } + Some(Commands::Debug { + file, + break_at_start, + }) => { + commands::debug(&file, break_at_start)?; + } + } + + Ok(()) +} diff --git a/crates/ppvm-cli/src/repl.rs b/crates/ppvm-cli/src/repl.rs new file mode 100644 index 000000000..9dfedeb5c --- /dev/null +++ b/crates/ppvm-cli/src/repl.rs @@ -0,0 +1,395 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Interactive REPL — a quantum-circuit playground. Allocate a fixed-size +//! device with `device N`, then apply gates and measurements one line at a time +//! against a single persistent machine, seeing measurement outcomes inline. + +use eyre::{Result, WrapErr, bail, eyre}; +use ppvm_vihaco::CircuitInstruction; +use ppvm_vihaco::composite::PPVM; +use ppvm_vihaco::measurements::MeasurementResult; +use rustyline::DefaultEditor; +use rustyline::error::ReadlineError; +#[cfg(test)] +use std::io::BufRead; +use std::io::Write; +use std::path::PathBuf; + +/// How a gate command lowers: the engine instruction plus how many qubit and +/// float operands it consumes (qubits first, then floats — the order +/// `apply_circuit_instruction` expects). +struct GateSpec { + inst: CircuitInstruction, + qubits: usize, + floats: usize, +} + +/// Resolve a gate command name to its spec, or `None` if it is not a gate. +/// `TwoQubitPauliError` is intentionally absent — its tableau arm is `todo!()`. +fn gate_spec(name: &str) -> Option { + use CircuitInstruction::*; + let (inst, qubits, floats) = match name { + "x" => (X, 1, 0), + "y" => (Y, 1, 0), + "z" => (Z, 1, 0), + "h" => (H, 1, 0), + "s" => (S, 1, 0), + "sadj" => (SAdj, 1, 0), + "sqrtx" => (SqrtX, 1, 0), + "sqrty" => (SqrtY, 1, 0), + "sqrtxadj" => (SqrtXAdj, 1, 0), + "sqrtyadj" => (SqrtYAdj, 1, 0), + "t" => (T, 1, 0), + "tadj" => (TAdj, 1, 0), + "measure" => (Measure, 1, 0), + "reset" => (Reset, 1, 0), + "cnot" => (CNOT, 2, 0), + "cz" => (CZ, 2, 0), + "rx" => (RX, 1, 1), + "ry" => (RY, 1, 1), + "rz" => (RZ, 1, 1), + "r" => (R, 1, 2), + "rxx" => (RXX, 2, 1), + "ryy" => (RYY, 2, 1), + "rzz" => (RZZ, 2, 1), + "u3" => (U3, 1, 3), + "depolarize" => (Depolarize, 1, 1), + "depolarize2" => (Depolarize2, 2, 1), + "loss" => (Loss, 1, 1), + "paulierror" => (PauliError, 1, 3), + "correlatedloss" => (CorrelatedLoss, 2, 3), + _ => return None, + }; + Some(GateSpec { + inst, + qubits, + floats, + }) +} + +/// Whether the loop should continue prompting or exit. +enum Outcome { + Continue, + Quit, +} + +/// Launch the interactive REPL with line editing and command history. +/// History recall (up/down arrows), cursor movement, and Ctrl-R search come +/// from rustyline; the per-command logic is the same `dispatch` the scripted +/// tests drive through `repl_loop`. +pub fn repl() -> Result<()> { + let mut rl = DefaultEditor::new()?; + let history = history_path(); + if let Some(path) = &history { + let _ = rl.load_history(path); // best-effort: a missing file is fine + } + + let mut machine: Option = None; + let mut output = std::io::stdout(); + loop { + match rl.readline("ppvm> ") { + Ok(line) => { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + let _ = rl.add_history_entry(trimmed); + match dispatch(trimmed, &mut machine, &mut output) { + Ok(Outcome::Continue) => {} + Ok(Outcome::Quit) => break, + Err(e) => writeln!(output, "error: {e}")?, + } + } + // Ctrl-C: abandon the current line, keep the session (shell-like). + Err(ReadlineError::Interrupted) => continue, + // Ctrl-D on an empty line: leave cleanly. + Err(ReadlineError::Eof) => break, + Err(e) => return Err(e).wrap_err("readline failed"), + } + } + + if let Some(path) = &history { + let _ = rl.save_history(path); // best-effort: don't fail the session + } + Ok(()) +} + +/// Where to persist history across sessions: `$HOME/.ppvm_history`. `None` +/// (so history is session-only) when there's no `HOME` — e.g. on Windows, +/// where you'd reach for the `dirs` crate instead. +fn history_path() -> Option { + std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".ppvm_history")) +} + +/// Core REPL loop, generic over its IO so tests can drive it with scripted +/// input. Holds a single `Option` — `None` until `device N`. Command-level +/// errors are printed and the loop continues; only `quit`/`exit`/EOF exit. +/// +/// Test-only: the interactive entry point is `repl`, which runs the same +/// `dispatch` under a rustyline editor for history and line editing. +#[cfg(test)] +fn repl_loop(input: &mut impl BufRead, output: &mut impl Write) -> Result<()> { + let mut machine: Option = None; + loop { + write!(output, "ppvm> ")?; + output.flush()?; + + let mut line = String::new(); + if input.read_line(&mut line)? == 0 { + // EOF: leave cleanly, ending the dangling prompt line. + writeln!(output)?; + break; + } + let line = line.trim(); + if line.is_empty() { + continue; + } + + match dispatch(line, &mut machine, output) { + Ok(Outcome::Continue) => {} + Ok(Outcome::Quit) => break, + Err(e) => writeln!(output, "error: {e}")?, + } + } + Ok(()) +} + +/// Parse and run one command line. +fn dispatch(line: &str, machine: &mut Option, output: &mut impl Write) -> Result { + let mut tokens = line.split_whitespace(); + let cmd = tokens.next().expect("line is non-empty after trim"); + let args: Vec<&str> = tokens.collect(); + + match cmd { + "quit" | "exit" => return Ok(Outcome::Quit), + "help" => print_help(output)?, + "device" => cmd_device(&args, machine, output)?, + "show" => cmd_show(machine, output)?, + _ => cmd_gate(cmd, &args, machine, output)?, + } + Ok(Outcome::Continue) +} + +/// `device N` — (re)create a fresh N-qubit device, discarding any prior state. +fn cmd_device(args: &[&str], machine: &mut Option, output: &mut impl Write) -> Result<()> { + let [n] = args else { + bail!("usage: device N"); + }; + let n: usize = n + .parse() + .wrap_err_with(|| format!("invalid qubit count {n:?}"))?; + if n == 0 { + bail!("device must have at least 1 qubit"); + } + + let existed = machine.is_some(); + *machine = Some(PPVM::with_qubits(n)?); + if existed { + writeln!( + output, + "ok: fresh {n}-qubit device (previous state discarded)" + )?; + } else { + writeln!(output, "ok: fresh {n}-qubit device")?; + } + Ok(()) +} + +/// `show` — print the current tableau / Pauli state. +fn cmd_show(machine: &mut Option, output: &mut impl Write) -> Result<()> { + let machine = require_device(machine)?; + writeln!(output, "{}", machine.state_string())?; + Ok(()) +} + +/// A gate command: ` [param…]`. Applies the gate and, if it +/// produced any measurement outcomes, prints them as `=> `. +fn cmd_gate( + name: &str, + args: &[&str], + machine: &mut Option, + output: &mut impl Write, +) -> Result<()> { + let spec = gate_spec(name).ok_or_else(|| eyre!("unknown command {name:?}; try \"help\""))?; + let machine = require_device(machine)?; + + let expected = spec.qubits + spec.floats; + if args.len() != expected { + bail!( + "{name} takes {} qubit(s) and {} param(s), got {} argument(s)", + spec.qubits, + spec.floats, + args.len() + ); + } + + let (qubit_args, param_args) = args.split_at(spec.qubits); + let qubits = qubit_args + .iter() + .map(|t| { + t.parse::() + .wrap_err_with(|| format!("invalid qubit index {t:?}")) + }) + .collect::>>()?; + let params = param_args + .iter() + .map(|t| { + t.parse::() + .wrap_err_with(|| format!("invalid parameter {t:?}")) + }) + .collect::>>()?; + + // New entries in the measurement record are this command's outcomes. + let before = machine.measurement_record().len(); + machine.apply_circuit_instruction(spec.inst, &qubits, ¶ms)?; + let record = machine.measurement_record(); + if record.len() > before { + writeln!(output, "=> {}", format_outcomes(&record[before..]))?; + } + Ok(()) +} + +/// Borrow the device, or error if none has been allocated yet. +fn require_device(machine: &mut Option) -> Result<&mut PPVM> { + machine + .as_mut() + .ok_or_else(|| eyre!("no device; run \"device N\" first")) +} + +/// Render measurement outcomes as a flat bit string: `0`/`1`, lost qubit = `2` +/// (the same convention as the `run` command's output). +fn format_outcomes(records: &[MeasurementResult]) -> String { + records + .iter() + .flatten() + .map(|outcome| char::from(b'0' + *outcome as u8)) + .collect() +} + +fn print_help(output: &mut impl Write) -> Result<()> { + writeln!(output, "meta:")?; + writeln!( + output, + " device N (re)create a fresh N-qubit device" + )?; + writeln!(output, " show print the current state")?; + writeln!(output, " help show this help")?; + writeln!(output, " quit | exit | EOF leave the REPL")?; + writeln!( + output, + "gates ( = qubit index, angles/probs are floats):" + )?; + writeln!( + output, + " x y z h s sadj sqrtx sqrty sqrtxadj sqrtyadj t tadj reset measure " + )?; + writeln!(output, " cnot | cz ")?; + writeln!(output, " rx ry rz | r ")?; + writeln!(output, " u3 ")?; + writeln!(output, " rxx ryy rzz ")?; + writeln!( + output, + " depolarize loss

| depolarize2

" + )?; + writeln!( + output, + " paulierror | correlatedloss " + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Drive the loop with a scripted session, returning everything it wrote. + fn session(script: &str) -> String { + let mut input = std::io::Cursor::new(script.as_bytes().to_vec()); + let mut output: Vec = Vec::new(); + repl_loop(&mut input, &mut output).unwrap(); + String::from_utf8(output).unwrap() + } + + #[test] + fn device_then_x_then_measure_is_one() { + let out = session("device 1\nx 0\nmeasure 0\nquit\n"); + assert!(out.contains("ok: fresh 1-qubit device"), "{out}"); + assert!(out.contains("=> 1"), "X|0> measured should be 1:\n{out}"); + } + + #[test] + fn fresh_measure_is_zero() { + let out = session("device 1\nmeasure 0\nquit\n"); + assert!(out.contains("=> 0"), "|0> measured should be 0:\n{out}"); + } + + #[test] + fn gate_before_device_errors_and_continues() { + // The error is printed and the loop keeps going: the later device+measure + // still works. + let out = session("x 0\ndevice 1\nmeasure 0\nquit\n"); + assert!(out.contains("no device"), "{out}"); + assert!( + out.contains("=> 0"), + "loop should continue after the error:\n{out}" + ); + } + + #[test] + fn device_twice_reports_discarded_state() { + let out = session("device 1\ndevice 2\nquit\n"); + assert!(out.contains("previous state discarded"), "{out}"); + } + + #[test] + fn show_renders_the_state() { + let out = session("device 1\nshow\nquit\n"); + let expected = PPVM::with_qubits(1).unwrap().state_string(); + assert!(out.contains(expected.trim()), "show output missing:\n{out}"); + } + + #[test] + fn unknown_command_errors_and_continues() { + let out = session("device 1\nbogus 0\nmeasure 0\nquit\n"); + assert!(out.contains("unknown command"), "{out}"); + assert!(out.contains("=> 0"), "loop should continue:\n{out}"); + } + + #[test] + fn eof_exits_cleanly() { + // No quit line: the session ends at EOF without hanging or erroring. + let out = session("device 1\n"); + assert!(out.contains("ok: fresh 1-qubit device"), "{out}"); + } + + #[test] + fn cnot_respects_control_and_target_order() { + // x 0 -> |10>; cnot 0 1 (control q0=1) flips q1 -> |11>. Both measure 1. + // If control/target were swapped, q1 would stay 0. + let out = session("device 2\nx 0\ncnot 0 1\nmeasure 0\nmeasure 1\nquit\n"); + // Each scripted line follows a "ppvm> " prompt with no echo, so a result + // line reads "ppvm> => 1"; take the text after "=> ". + let measurements: Vec<&str> = out.lines().filter_map(|l| l.split("=> ").nth(1)).collect(); + assert_eq!(measurements, vec!["1", "1"], "{out}"); + } + + #[test] + fn two_qubit_float_gate_runs() { + // rxx (2 qubits + 1 float) should parse and apply without error. + let out = session("device 2\nrxx 0 1 0.5\nquit\n"); + assert!(!out.contains("error"), "rxx should run cleanly:\n{out}"); + } + + #[test] + fn bad_arity_errors() { + let out = session("device 1\nx\nquit\n"); + assert!(out.contains("takes"), "arity error expected:\n{out}"); + } + + #[test] + fn out_of_range_qubit_errors_not_panics() { + let out = session("device 1\nx 3\nquit\n"); + assert!(out.contains("out of range"), "{out}"); + } +} diff --git a/crates/ppvm-pauli-sum/src/sum/rot1.rs b/crates/ppvm-pauli-sum/src/sum/rot1.rs index d763de73a..3f21a6fb7 100644 --- a/crates/ppvm-pauli-sum/src/sum/rot1.rs +++ b/crates/ppvm-pauli-sum/src/sum/rot1.rs @@ -128,6 +128,20 @@ where } } +impl RotXY for PauliSum +where + PauliSum: RotationOne, +{ + fn r(&mut self, addr0: usize, axis_angle: ::Coeff, theta: ::Coeff) { + // R(axis_angle, θ) = RZ(axis_angle)·RX(θ)·RZ(−axis_angle). PauliSum runs + // in the Heisenberg picture (observables propagate backward), so the + // sub-rotations are emitted in reverse of the tableau's forward order. + self.rz(addr0, axis_angle.clone()); + self.rx(addr0, theta); + self.rz(addr0, -axis_angle); + } +} + /// 2-bit Pauli code: 00 I, 01 X, 10 Z, 11 Y /// Returns \(ε, k\) so that –i \[P_i, P_j\]/2 = ε · P_k. /// For every commuting pair it yields (0, 0). @@ -236,6 +250,32 @@ mod tests { assert_eq!(answer, expect); } + #[test] + fn test_r() { + use std::f64::consts::FRAC_PI_2; + let theta = 2.1; + + // r(axis_angle=0, θ) == rx(θ). + let mut via_r: PauliSum> = PauliSum::builder().n_qubits(1).build(); + via_r += ("Z", 1.0); + via_r.r(0, 0.0, theta); + let mut via_rx: PauliSum> = PauliSum::builder().n_qubits(1).build(); + via_rx += ("Z", 1.0); + via_rx.rx(0, theta); + assert!((via_r.overlap(&via_rx) - 1.0).abs() < 1e-9); + + // r(axis_angle=π/2, θ) must equal ry(θ) — NOT ry(−θ). This is the case + // that distinguishes the Heisenberg (backward) order from the + // Schrödinger one: a forward-ordered impl would yield ry(−θ) here. + let mut via_r: PauliSum> = PauliSum::builder().n_qubits(1).build(); + via_r += ("Z", 1.0); + via_r.r(0, FRAC_PI_2, theta); + let mut via_ry: PauliSum> = PauliSum::builder().n_qubits(1).build(); + via_ry += ("Z", 1.0); + via_ry.ry(0, theta); + assert!((via_r.overlap(&via_ry) - 1.0).abs() < 1e-9); + } + #[test] fn test_rz() { let mut answer: PauliSum> = PauliSum::builder().n_qubits(1).build(); diff --git a/crates/ppvm-python-native/ppvm_python_native.pyi b/crates/ppvm-python-native/ppvm_python_native.pyi new file mode 100644 index 000000000..20ebe6e38 --- /dev/null +++ b/crates/ppvm-python-native/ppvm_python_native.pyi @@ -0,0 +1,196 @@ +from typing import Sequence + + +class _PauliSumBase: + def __init__( + self, + n_qubits: int, + min_abs_coeff: float = 1e-10, + max_pauli_weight: int = ..., + max_loss_weight: int = ..., + terms: list[str] = ..., + coefficients: list[float] = ..., + ) -> None: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + def __copy__(self) -> "_PauliSumBase": ... + def __deepcopy__(self, memo: object) -> "_PauliSumBase": ... + def __len__(self) -> int: ... + def trace(self, pattern: str) -> float: ... + def overlap_with_zero(self) -> float: ... + def overlap(self, other: "_PauliSumBase") -> float: ... + def x(self, addr0: int) -> None: ... + def y(self, addr0: int) -> None: ... + def z(self, addr0: int) -> None: ... + def h(self, addr0: int) -> None: ... + def s(self, addr0: int) -> None: ... + def cnot(self, addr0: int, addr1: int) -> None: ... + def cz(self, addr0: int, addr1: int) -> None: ... + def s_adj(self, addr0: int) -> None: ... + def sqrt_x(self, addr0: int) -> None: ... + def sqrt_y(self, addr0: int) -> None: ... + def sqrt_x_adj(self, addr0: int) -> None: ... + def sqrt_y_adj(self, addr0: int) -> None: ... + def rx(self, addr0: int, theta: float) -> None: ... + def ry(self, addr0: int, theta: float) -> None: ... + def rz(self, addr0: int, theta: float) -> None: ... + def r(self, addr0: int, axis_angle: float, theta: float) -> None: ... + def rxx(self, addr0: int, addr1: int, theta: float) -> None: ... + def ryy(self, addr0: int, addr1: int, theta: float) -> None: ... + def rzz(self, addr0: int, addr1: int, theta: float) -> None: ... + def pauli_error(self, addr0: int, p: Sequence[float]) -> None: ... + def two_qubit_pauli_error( + self, addr0: int, addr1: int, p: Sequence[float] + ) -> None: ... + def depolarize(self, addr0: int, p: float) -> None: ... + def depolarize2(self, addr0: int, addr1: int, p: float) -> None: ... + def amplitude_damping(self, addr0: int, gamma: float) -> None: ... + def terms(self) -> list[tuple[str, float]]: ... + def weights(self) -> list[tuple[str, int]]: ... + def current_max_weight(self) -> int: ... + + +class _PauliSumLossBase(_PauliSumBase): + def loss_channel(self, addr0: int, p: float) -> None: ... + def correlated_loss_channel( + self, addr0: int, addr1: int, p: Sequence[float] + ) -> None: ... + def reset_loss_channel(self, addr0: int) -> None: ... + + +class PauliSumIndexMapFxHash0(_PauliSumBase): ... +class PauliSumIndexMapFxHash1(_PauliSumBase): ... +class PauliSumIndexMapFxHash2(_PauliSumBase): ... +class PauliSumIndexMapFxHash3(_PauliSumBase): ... +class PauliSumIndexMapFxHash4(_PauliSumBase): ... +class PauliSumIndexMapFxHash5(_PauliSumBase): ... +class PauliSumIndexMapFxHash6(_PauliSumBase): ... +class PauliSumIndexMapFxHash7(_PauliSumBase): ... +class PauliSumIndexMapFxHash8(_PauliSumBase): ... +class PauliSumIndexMapFxHash9(_PauliSumBase): ... +class PauliSumIndexMapFxHash10(_PauliSumBase): ... +class PauliSumIndexMapFxHash11(_PauliSumBase): ... +class PauliSumIndexMapFxHash12(_PauliSumBase): ... +class PauliSumIndexMapFxHash13(_PauliSumBase): ... +class PauliSumIndexMapFxHash14(_PauliSumBase): ... +class PauliSumIndexMapFxHash15(_PauliSumBase): ... + +class PauliSumLossIndexMapFxHash0(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash1(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash2(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash3(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash4(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash5(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash6(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash7(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash8(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash9(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash10(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash11(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash12(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash13(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash14(_PauliSumLossBase): ... +class PauliSumLossIndexMapFxHash15(_PauliSumLossBase): ... + + +class _GeneralizedTableauBase: + def __init__( + self, + n_qubits: int, + min_abs_coeff: float = 1e-10, + seed: int | None = None, + ) -> None: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + def __copy__(self) -> "_GeneralizedTableauBase": ... + def __deepcopy__(self, memo: object) -> "_GeneralizedTableauBase": ... + def measure(self, addr0: int) -> bool | None: ... + def x(self, addr0: int) -> None: ... + def y(self, addr0: int) -> None: ... + def z(self, addr0: int) -> None: ... + def h(self, addr0: int) -> None: ... + def s(self, addr0: int) -> None: ... + def s_adj(self, addr0: int) -> None: ... + def sqrt_x(self, addr0: int) -> None: ... + def sqrt_x_adj(self, addr0: int) -> None: ... + def sqrt_y(self, addr0: int) -> None: ... + def sqrt_y_adj(self, addr0: int) -> None: ... + def cnot(self, addr0: int, addr1: int) -> None: ... + def cy(self, addr0: int, addr1: int) -> None: ... + def cz(self, addr0: int, addr1: int) -> None: ... + def t(self, addr0: int) -> None: ... + def t_adj(self, addr0: int) -> None: ... + def rx(self, addr0: int, theta: float) -> None: ... + def ry(self, addr0: int, theta: float) -> None: ... + def rz(self, addr0: int, theta: float) -> None: ... + def u3(self, addr0: int, theta: float, phi: float, lam: float) -> None: ... + def r(self, addr0: int, axis_angle: float, theta: float) -> None: ... + def rxx(self, addr0: int, addr1: int, theta: float) -> None: ... + def ryy(self, addr0: int, addr1: int, theta: float) -> None: ... + def rzz(self, addr0: int, addr1: int, theta: float) -> None: ... + def pauli_error(self, addr0: int, p: Sequence[float]) -> None: ... + def depolarize(self, addr0: int, p: float) -> None: ... + def depolarize2(self, addr0: int, addr1: int, p: float) -> None: ... + def two_qubit_pauli_error( + self, addr0: int, addr1: int, p: Sequence[float] + ) -> None: ... + def loss_channel(self, addr0: int, p: float) -> None: ... + def correlated_loss_channel( + self, addr0: int, addr1: int, p: Sequence[float] + ) -> None: ... + def reset_loss_channel(self, addr0: int) -> None: ... + def reset(self, addr0: int) -> None: ... + def is_lost(self, addr0: int) -> bool: ... + def loss_values(self) -> list[bool]: ... + def run(self, prog: "StimProgram") -> list[int]: ... + @staticmethod + def sample( + prog: "StimProgram", + n_qubits: int, + min_abs_coeff: float = 1e-10, + num_shots: int = 1, + seed: int | None = None, + ) -> list[list[int]]: ... + def fork(self, seed: int | None = None) -> "_GeneralizedTableauBase": ... + + +class StimProgram: + @staticmethod + def parse(src: str) -> "StimProgram": ... + @staticmethod + def from_file(path: str) -> "StimProgram": ... + def __repr__(self) -> str: ... + + +class GeneralizedTableau1(_GeneralizedTableauBase): ... +class GeneralizedTableau2(_GeneralizedTableauBase): ... +class GeneralizedTableau3(_GeneralizedTableauBase): ... +class GeneralizedTableau4(_GeneralizedTableauBase): ... +class GeneralizedTableau5(_GeneralizedTableauBase): ... +class GeneralizedTableau6(_GeneralizedTableauBase): ... +class GeneralizedTableau7(_GeneralizedTableauBase): ... +class GeneralizedTableau8(_GeneralizedTableauBase): ... +class GeneralizedTableau9(_GeneralizedTableauBase): ... +class GeneralizedTableau10(_GeneralizedTableauBase): ... +class GeneralizedTableau11(_GeneralizedTableauBase): ... +class GeneralizedTableau12(_GeneralizedTableauBase): ... +class GeneralizedTableau13(_GeneralizedTableauBase): ... +class GeneralizedTableau14(_GeneralizedTableauBase): ... +class GeneralizedTableau15(_GeneralizedTableauBase): ... +class GeneralizedTableau16(_GeneralizedTableauBase): ... +class GeneralizedTableau17(_GeneralizedTableauBase): ... +class GeneralizedTableau18(_GeneralizedTableauBase): ... +class GeneralizedTableau19(_GeneralizedTableauBase): ... +class GeneralizedTableau20(_GeneralizedTableauBase): ... +class GeneralizedTableau21(_GeneralizedTableauBase): ... +class GeneralizedTableau22(_GeneralizedTableauBase): ... +class GeneralizedTableau23(_GeneralizedTableauBase): ... +class GeneralizedTableau24(_GeneralizedTableauBase): ... +class GeneralizedTableau25(_GeneralizedTableauBase): ... +class GeneralizedTableau26(_GeneralizedTableauBase): ... +class GeneralizedTableau27(_GeneralizedTableauBase): ... +class GeneralizedTableau28(_GeneralizedTableauBase): ... +class GeneralizedTableau29(_GeneralizedTableauBase): ... +class GeneralizedTableau30(_GeneralizedTableauBase): ... +class GeneralizedTableau31(_GeneralizedTableauBase): ... +class GeneralizedTableau32(_GeneralizedTableauBase): ... diff --git a/crates/ppvm-python-native/src/interface.rs b/crates/ppvm-python-native/src/interface.rs index 4992c6c54..44a47b83d 100644 --- a/crates/ppvm-python-native/src/interface.rs +++ b/crates/ppvm-python-native/src/interface.rs @@ -278,6 +278,12 @@ macro_rules! create_interface { if truncate { self.inner.truncate(); } } + #[pyo3(signature = (addr0, axis_angle, theta, truncate = true))] + pub fn r(&mut self, addr0: usize, axis_angle: f64, theta: f64, truncate: bool) { + self.inner.r(addr0, axis_angle, theta); + if truncate { self.inner.truncate(); } + } + // rot2 #[pyo3(signature = (targets, theta, truncate = true))] pub fn rxx(&mut self, targets: Vec, theta: f64, truncate: bool) -> PyResult<()> { diff --git a/crates/ppvm-python-native/src/interface_tableau.rs b/crates/ppvm-python-native/src/interface_tableau.rs index 81c155610..d4858a256 100644 --- a/crates/ppvm-python-native/src/interface_tableau.rs +++ b/crates/ppvm-python-native/src/interface_tableau.rs @@ -186,6 +186,10 @@ macro_rules! create_interface { self.inner.u3(addr0, theta, phi, lam); } + pub fn r(&mut self, addr0: usize, axis_angle: f64, theta: f64) { + self.inner.r(addr0, axis_angle, theta); + } + // rot2 pub fn rxx(&mut self, targets: Vec, theta: f64) -> PyResult<()> { let pairs = crate::flat_pairs(&targets)?; diff --git a/crates/ppvm-runtime/src/traits/branch/rot1.rs b/crates/ppvm-runtime/src/traits/branch/rot1.rs new file mode 100644 index 000000000..d610a601f --- /dev/null +++ b/crates/ppvm-runtime/src/traits/branch/rot1.rs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use crate::char::Pauli; +use crate::config::Config; + +/// Single-qubit Pauli rotations `exp(-i θ/2 · P)`. +pub trait RotationOne { + /// Rotate about `axis` (one of `X`, `Y`, `Z`) by angle `theta`. + fn rotate_1(&mut self, axis: Pauli, addr0: usize, theta: T::Coeff); + /// `RX(θ)` on qubit `addr0`. + fn rx(&mut self, addr0: usize, theta: impl Into) { + self.rotate_1(Pauli::X, addr0, theta.into()) + } + /// `RY(θ)` on qubit `addr0`. + fn ry(&mut self, addr0: usize, theta: impl Into) { + self.rotate_1(Pauli::Y, addr0, theta.into()) + } + /// `RZ(θ)` on qubit `addr0`. + fn rz(&mut self, addr0: usize, theta: impl Into) { + self.rotate_1(Pauli::Z, addr0, theta.into()) + } +} + +/// Rotation about an axis in the x/y plane: +/// `R(axis_angle, θ) = exp(-i θ/2 · (cos(axis_angle)·X + sin(axis_angle)·Y))`. +/// +/// The in-plane axis is `X` rotated about `Z` by `axis_angle`, so +/// `R(axis_angle, θ) = RZ(axis_angle)·RX(θ)·RZ(−axis_angle)`. +pub trait RotXY { + /// `R(axis_angle, θ)` on qubit `addr0`. + fn r(&mut self, addr0: usize, axis_angle: T::Coeff, theta: T::Coeff); +} diff --git a/crates/ppvm-tableau/Cargo.toml b/crates/ppvm-tableau/Cargo.toml index 72826d5a9..6b5c4dc07 100644 --- a/crates/ppvm-tableau/Cargo.toml +++ b/crates/ppvm-tableau/Cargo.toml @@ -17,6 +17,7 @@ ppvm-traits = { version = "0.1.0", path = "../ppvm-traits" } ppvm-pauli-sum = { version = "0.1.0", path = "../ppvm-pauli-sum" } rand = "0.10.1" smallvec = "1.15" +ppvm-pauli-word = { version = "0.1.0", path = "../ppvm-pauli-word" } # rayon needs OS threads — native only. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/ppvm-tableau/src/data.rs b/crates/ppvm-tableau/src/data.rs index d35e3e700..52443a980 100644 --- a/crates/ppvm-tableau/src/data.rs +++ b/crates/ppvm-tableau/src/data.rs @@ -54,8 +54,7 @@ pub struct Tableau { } impl Tableau { - /// Construct a fresh tableau initialised to `|0…0⟩`. - pub fn new(n_qubits: usize) -> Self { + fn new_data(n_qubits: usize) -> Vec> { // Initialize tableau for 0 state let mut data: Vec> = Vec::with_capacity(2 * n_qubits); @@ -72,7 +71,11 @@ impl Tableau { pw.set(i, Pauli::Z); data.push(pw); } + data + } + pub fn new(n_qubits: usize) -> Self { + let data = Tableau::::new_data(n_qubits); Self { n_qubits, data, @@ -87,6 +90,11 @@ impl Tableau { t } + pub fn reset_all(&mut self) { + let data = Tableau::::new_data(self.n_qubits); + self.data = data; + } + /// View of the stabilizer rows (the upper half of the tableau). #[inline] pub fn stabilizers(&self) -> &[PhasedPauliWordNoHash] { @@ -537,7 +545,9 @@ where Complex: std::ops::Mul> + std::ops::AddAssign + From + Copy, { - if items.len() >= RAYON_COEFF_THRESHOLD { + // See `branch_coefficients_parallel`: avoid nesting rayon inside shot-level + // parallelism; the main-thread (single-shot) path is unaffected. + if items.len() >= RAYON_COEFF_THRESHOLD && rayon::current_thread_index().is_none() { use rayon::prelude::*; return items @@ -675,6 +685,21 @@ where s } + pub fn reset_all(&mut self) { + self.tableau.reset_all(); + + let mut coefficients = C::new(); + let complex_one = Complex { + re: T::Coeff::one(), + im: T::Coeff::zero(), + }; + coefficients.unsafe_insert(I::zero(), complex_one); + self.coefficients = coefficients; + for l in self.is_lost.iter_mut() { + *l &= false; + } + } + /// Clone the quantum state but reinitialize the RNG, producing an independent simulation /// branch. If `seed` is `Some`, the new RNG is seeded deterministically; if `None`, it is /// seeded from OS entropy. @@ -848,6 +873,36 @@ where (p_word.phase, stab_anticomm_bits, destab_anticomm_bits) } + /// Multi-qubit generalization of [`compute_decomposition`]: conjugate an + /// arbitrary `PauliWord` through the tableau and return the same triple + /// `(phase, stab_anticomm_bits, destab_anticomm_bits)`. + /// + /// Algorithm: call [`compute_decomposition`] for each non-identity qubit + /// in the input, then multiply the resulting single-qubit conjugates in + /// canonical-basis form `i^φ X^x Z^z`. Pauli multiplication picks up a + /// `(-1)^{popcount(z_running & x_new)}` cross-phase from + /// `Z^z_a X^x_b = (-1)^{z_a · x_b} X^x_b Z^z_a`. + pub(crate) fn compute_decomposition_word(&self, word: &W) -> (u8, I, I) + where + <::Storage as BitView>::Store: PrimInt, + { + let mut phase = 0u8; + let mut stab_anticomm = I::zero(); + let mut destab_anticomm = I::zero(); + for q in 0..self.n_qubits() { + let p_q = word.get(q); + if p_q == Pauli::I { + continue; + } + let (q_phase, q_stab, q_destab) = self.compute_decomposition(q, p_q); + let cross = 2 * (symplectic_inner(destab_anticomm, q_stab) as u8 % 2); + phase = (phase + q_phase + cross) % 4; + stab_anticomm = stab_anticomm ^ q_stab; + destab_anticomm = destab_anticomm ^ q_destab; + } + (phase, stab_anticomm, destab_anticomm) + } + /// every basis index is a bit string alpha defining the basis state /// the phase when applying a Pauli is the product of all destabilizer phases /// and the phase contributions from the commutation relations diff --git a/crates/ppvm-tableau/src/expectation.rs b/crates/ppvm-tableau/src/expectation.rs new file mode 100644 index 000000000..9a69d596c --- /dev/null +++ b/crates/ppvm-tableau/src/expectation.rs @@ -0,0 +1,316 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Pauli-string expectation values for `GeneralizedTableau`. +//! +//! Two entry points: +//! +//! - [`GeneralizedTableau::expectation`] — single-Pauli `⟨ψ|P|ψ⟩` for a +//! `PauliWord`. Conjugates `P` through the tableau and overlaps the +//! resulting Pauli with the sparse coefficient vector using the same +//! formulas as the measurement code. +//! - [`GeneralizedTableau::trace`] — `Σ_{P matches pattern} ⟨ψ|P|ψ⟩` for a +//! `PauliPattern`. Enumerates the matching Paulis and sums their +//! expectations. +//! +//! Decision 9 in the multi-backend plan calls these out as the natural +//! primitive for the tableau backend; semantics intentionally diverge from +//! the PauliSum trace. + +use crate::data::GeneralizedTableau; +use crate::prelude::*; +use bitvec::view::BitView; +use fxhash::FxHashMap as HashMap; +use num::PrimInt; +use num::complex::{Complex, Complex64, ComplexFloat}; +use num::traits::{One, ToPrimitive, Zero}; +use ppvm_pauli_word::pattern::PauliPattern; +use std::fmt::Debug; + +impl GeneralizedTableau +where + T: Config, + <::Storage as BitView>::Store: PrimInt, + C: SparseVector, I> + Debug, + T::Coeff: One + + Zero + + Clone + + num::Num + + ToPrimitive + + Debug + + std::ops::Mul + + PartialOrd + + Send + + Sync, + Complex: std::ops::Mul> + + From + + std::ops::MulAssign + + std::ops::AddAssign + + One + + ComplexFloat + + Copy, + I: TableauIndex + Debug + Send + Sync, +{ + /// `⟨ψ|word|ψ⟩` for the multi-qubit Pauli `word`. + /// + /// Conjugates `word` through the Clifford tableau (giving a Pauli on the + /// canonical basis: an X-mask, Z-mask, and `i^φ` phase), then sums + /// `⟨α|P_conj|β⟩ c_α* c_β` over the sparse coefficient vector. Always + /// returns a real number (Hermitian operator on a normalized state). + pub fn expectation(&self, word: &W) -> f64 { + let (phase, stab_anticomm, destab_anticomm) = self.compute_decomposition_word(word); + let entries: Vec<(Complex, I)> = self.coefficients.clone().into_iter().collect(); + if stab_anticomm == I::zero() { + Self::compute_overlap_case_b(&entries, phase, destab_anticomm) + } else { + let coeff_map: HashMap> = + entries.into_iter().map(|(v, i)| (i, v)).collect(); + let odd_phase_mask = self.odd_phase_destabilizer_mask(); + Self::compute_overlap_case_a( + &coeff_map, + phase, + destab_anticomm, + stab_anticomm, + odd_phase_mask, + ) + } + } + + /// `Σ_{P matches pattern} ⟨ψ|P|ψ⟩`. + /// + /// Enumerates every `PauliWord` accepted by `pattern` via + /// [`PauliPattern::enumerate_matches`] and sums their expectations. + /// Star quantifiers (`X*`) panic — the pattern must be bounded; use + /// counted repetition (`Z?{n}`) or positional anchors instead. + pub fn trace(&self, pattern: &PauliPattern) -> f64 { + let mut sum = 0.0f64; + for word in pattern.enumerate_matches::(self.n_qubits()) { + sum += self.expectation(&word); + } + sum + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ppvm_pauli_sum::config::fxhash::ByteF64; + + type TestTableau = GeneralizedTableau>; + + fn word(s: &str) -> PauliWord { + s.into() + } + + fn assert_close(actual: f64, expected: f64, tol: f64) { + assert!( + (actual - expected).abs() < tol, + "expected {expected}, got {actual} (|Δ| = {})", + (actual - expected).abs() + ); + } + + // ─── Single-qubit expectations ────────────────────────────────────── + + #[test] + fn expectation_z_on_zero_state_is_one() { + let tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + assert_close(tab.expectation(&word("Z")), 1.0, 1e-12); + } + + #[test] + fn expectation_x_on_zero_state_is_zero() { + let tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + assert_close(tab.expectation(&word("X")), 0.0, 1e-12); + } + + #[test] + fn expectation_identity_on_zero_state_is_one() { + let tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + assert_close(tab.expectation(&word("I")), 1.0, 1e-12); + } + + #[test] + fn expectation_x_on_plus_state_is_one() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.h(0); + assert_close(tab.expectation(&word("X")), 1.0, 1e-12); + assert_close(tab.expectation(&word("Z")), 0.0, 1e-12); + } + + // ─── Bell state ⟨Φ+|·|Φ+⟩ ──────────────────────────────────────────── + + fn bell() -> TestTableau { + let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12); + tab.h(0); + tab.cnot(0, 1); + tab + } + + #[test] + fn bell_state_pauli_expectations() { + let tab = bell(); + assert_close(tab.expectation(&word("II")), 1.0, 1e-12); + assert_close(tab.expectation(&word("ZZ")), 1.0, 1e-12); + assert_close(tab.expectation(&word("XX")), 1.0, 1e-12); + assert_close(tab.expectation(&word("YY")), -1.0, 1e-12); + // Cross terms vanish for the Bell state. + assert_close(tab.expectation(&word("IZ")), 0.0, 1e-12); + assert_close(tab.expectation(&word("ZI")), 0.0, 1e-12); + assert_close(tab.expectation(&word("XZ")), 0.0, 1e-12); + assert_close(tab.expectation(&word("YX")), 0.0, 1e-12); + } + + // ─── GHZ state ──────────────────────────────────────────────────── + + #[test] + fn ghz_state_expectations() { + let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12); + tab.h(0); + tab.cnot(0, 1); + tab.cnot(1, 2); + // GHZ = (|000⟩ + |111⟩)/√2. For Z^z: eigenvalue is (-1)^{popcount(z)·x} + // on |xxx⟩, so on the two basis states it agrees iff popcount(z) is + // even; the diagonal expectation is then +1, otherwise 0. + assert_close(tab.expectation(&word("III")), 1.0, 1e-12); // popcount 0 → +1 + assert_close(tab.expectation(&word("ZZZ")), 0.0, 1e-12); // popcount 3 → 0 + assert_close(tab.expectation(&word("ZIZ")), 1.0, 1e-12); // popcount 2 → +1 + assert_close(tab.expectation(&word("ZZI")), 1.0, 1e-12); // popcount 2 → +1 + assert_close(tab.expectation(&word("IZI")), 0.0, 1e-12); // popcount 1 → 0 + // XXX flips |000⟩ ↔ |111⟩, both in the GHZ superposition → +1. + assert_close(tab.expectation(&word("XXX")), 1.0, 1e-12); + // Y has off-diagonal action with imaginary phase; YYY contributes 0. + assert_close(tab.expectation(&word("YYY")), 0.0, 1e-12); + } + + // ─── Single-qubit rotation: |ψ⟩ = RY(θ)|0⟩ ──────────────────────── + + #[test] + fn ry_rotation_z_expectation_is_cos_theta() { + // RY(θ)|0⟩ = cos(θ/2)|0⟩ + sin(θ/2)|1⟩. ⟨ψ|Z|ψ⟩ = cos(θ). + for theta in [0.0, 0.3, 1.0, std::f64::consts::PI / 2.0] { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.ry(0, theta); + assert_close(tab.expectation(&word("Z")), theta.cos(), 1e-12); + assert_close(tab.expectation(&word("X")), theta.sin(), 1e-12); + } + } + + // ─── X / Y on a non-Clifford superposition ─────────────────────── + // + // After H(0); T(0) the state is |ψ⟩ = (|0⟩ + e^{iπ/4}|1⟩)/√2: + // ⟨ψ|X|ψ⟩ = cos(π/4) = √2/2, + // ⟨ψ|Y|ψ⟩ = sin(π/4) = √2/2. + // The T gate populates two branches in the sparse coefficient vector, + // so the overlap is a cross-product between them — case_a, not case_b. + // Y in particular drives `phase_decomp` to an odd value (Y on a |+⟩-style + // frame conjugates to -Y), forcing the `phase == 1 | 3` arms of + // `overlap_case_a`'s `match`. A sign bug there would flip ⟨Y⟩. + + #[test] + fn t_plus_x_expectation_is_cos_pi_over_4() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.h(0); + tab.t(0); + assert_close( + tab.expectation(&word("X")), + std::f64::consts::FRAC_1_SQRT_2, + 1e-12, + ); + } + + #[test] + fn t_plus_y_expectation_is_sin_pi_over_4() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.h(0); + tab.t(0); + assert_close( + tab.expectation(&word("Y")), + std::f64::consts::FRAC_1_SQRT_2, + 1e-12, + ); + } + + // ─── trace(pattern) ─────────────────────────────────────────────── + + #[test] + fn trace_of_z_or_identity_pattern_on_bell_is_two() { + // For |Φ+⟩ = (|00⟩+|11⟩)/√2: + // Σ_{P ∈ {I,Z}^2} ⟨Φ+|P|Φ+⟩ = ⟨II⟩ + ⟨IZ⟩ + ⟨ZI⟩ + ⟨ZZ⟩ + // = 1 + 0 + 0 + 1 = 2. + // Equivalently 2^n |⟨0…0|Φ+⟩|² = 4 · 1/2 = 2. + let tab = bell(); + let pat = PauliPattern::parse("Z?{2}").expect("parse Z?{2}"); + assert_close(tab.trace(&pat), 2.0, 1e-12); + } + + #[test] + fn trace_of_y_or_identity_pattern_on_bell_is_zero() { + // For |Φ+⟩, Σ_{P ∈ {I,Y}^2} ⟨Φ+|P|Φ+⟩ = ⟨II⟩ + ⟨IY⟩ + ⟨YI⟩ + ⟨YY⟩ + // = 1 + 0 + 0 + (-1) = 0. + // Equivalently 2^n |⟨+i+i|Φ+⟩|² = 4 · 0 = 0 — the projection onto + // the all-|+i⟩ state has zero amplitude. `trace` does the + // enumeration sum here without ever mutating state or calling + // `normalize`, so the zero-probability case doesn't panic. + let tab = bell(); + let pat = PauliPattern::parse("Y?{2}").expect("parse Y?{2}"); + assert_close(tab.trace(&pat), 0.0, 1e-12); + } + + #[test] + fn trace_of_positional_pattern_on_bell_matches_single_pauli() { + // `Z0Z1` matches exactly the word ZZ; trace should equal ⟨ZZ⟩ = 1. + let tab = bell(); + let pat = PauliPattern::parse("Z0Z1").expect("parse Z0Z1"); + assert_close(tab.trace(&pat), 1.0, 1e-12); + } + + // ─── Cross-backend: forward tableau shots vs backward PauliSum ──────── + // + // Run a noisy Clifford circuit many times on a tableau and average the + // per-shot ⟨ψ|ZZ|ψ⟩. Independently, Heisenberg-propagate ZZ backward + // through the same circuit via `PauliSum` and read off ⟨0…0|U†ZZ U|0…0⟩ + // as the sum of coefficients over Z/I-only Paulis. The Monte-Carlo + // average and the deterministic value must agree within sampling error. + // + // `PauliSum::g(i)` performs O → g† O g, so the backward sweep applies + // gates in reverse time order. Depolarize is self-dual under Heisenberg. + #[test] + fn forward_shots_match_backward_pauli_sum_under_depolarizing_noise() { + use ppvm_pauli_sum::config::indexmap::ByteFxHashF64; + + let p = 0.05_f64; + let n_shots: u64 = 4000; + let n_qubits = 2; + + let mut sum = 0.0_f64; + for shot in 0..n_shots { + let mut tab: TestTableau = GeneralizedTableau::new_with_seed(n_qubits, 1e-12, shot); + tab.h(0); + tab.depolarize1(0, p); + tab.cnot(0, 1); + tab.depolarize1(0, p); + tab.depolarize1(1, p); + sum += tab.expectation(&word("ZZ")); + } + let avg = sum / (n_shots as f64); + + let mut ps: PauliSum> = PauliSum::builder().n_qubits(n_qubits).build(); + ps += ("ZZ", 1.0); + ps.depolarize1(1, p); + ps.depolarize1(0, p); + ps.cnot(0, 1); + ps.depolarize1(0, p); + ps.h(0); + let z_or_i = PauliPattern::parse("Z?{2}").expect("parse Z?{2}"); + let exact = ps.trace(&z_or_i); + + // Per-shot |⟨ZZ⟩| ≤ 1 ⇒ σ_mean ≤ 1/√N; 5σ keeps this robust to RNG draws. + let tol = 5.0 / (n_shots as f64).sqrt(); + assert!( + (avg - exact).abs() < tol, + "tableau avg {avg} vs PauliSum exact {exact}, |Δ|={} (tol {tol})", + (avg - exact).abs() + ); + } +} diff --git a/crates/ppvm-tableau/src/gates/rot1.rs b/crates/ppvm-tableau/src/gates/rot1.rs index b0fff795c..3ee2cd978 100644 --- a/crates/ppvm-tableau/src/gates/rot1.rs +++ b/crates/ppvm-tableau/src/gates/rot1.rs @@ -42,6 +42,20 @@ where } } +impl, I>> RotXY for GeneralizedTableau +where + GeneralizedTableau: RotationOne, +{ + fn r(&mut self, addr0: usize, axis_angle: T::Coeff, theta: T::Coeff) { + // R(axis_angle, θ) = RZ(axis_angle)·RX(θ)·RZ(−axis_angle). The tableau + // runs in the Schrödinger picture, so the sub-rotations are applied in + // forward order: RZ(−axis_angle) first, then RX(θ), then RZ(axis_angle). + self.rz(addr0, -axis_angle.clone()); + self.rx(addr0, theta); + self.rz(addr0, axis_angle); + } +} + #[cfg(test)] mod tests { use super::*; @@ -112,6 +126,62 @@ mod tests { assert!(!tab.measure(0).unwrap()); } + /// R(axis_angle=0, θ=0) = identity: |0⟩ stays |0⟩, no branching. + #[test] + fn test_r_identity() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.r(0, 0.0, 0.0); + assert_eq!(tab.coefficients.len(), 1); + assert!(!tab.measure(0).unwrap()); + } + + /// R(axis_angle=0, θ=π) = RX(π): flips |0⟩ → |1⟩ with no branching. + #[test] + fn test_r_axis_zero_is_rx() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.r(0, 0.0, PI); + assert_eq!(tab.coefficients.len(), 1); + assert!(tab.measure(0).unwrap()); + } + + /// R(axis_angle=π/2, θ=π) = RY(π): flips |0⟩ → |1⟩ with no branching. + #[test] + fn test_r_axis_half_pi_is_ry() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.r(0, FRAC_PI_2, PI); + assert_eq!(tab.coefficients.len(), 1); + assert!(tab.measure(0).unwrap()); + } + + /// A partial rotation about an in-plane axis branches into two terms. + #[test] + fn test_r_branches() { + let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12); + tab.r(0, 0.37 * PI, FRAC_PI_2); + assert_eq!(tab.coefficients.len(), 2); + } + + /// R(axis_angle, θ) must give identical per-seed measurement statistics + /// to the manual decomposition RZ(axis_angle)·RX(θ)·RZ(−axis_angle). + #[test] + fn test_r_matches_rz_rx_rz() { + let (axis_angle, theta) = (0.21 * PI, 0.34 * PI); + + let mut tab_r: TestTableau = GeneralizedTableau::new_with_seed(1, 1e-12, 0); + tab_r.r(0, axis_angle, theta); + + let mut tab_manual: TestTableau = GeneralizedTableau::new_with_seed(1, 1e-12, 0); + tab_manual.rz(0, -axis_angle); + tab_manual.rx(0, theta); + tab_manual.rz(0, axis_angle); + + for seed in 0..200 { + let result_r = tab_r.fork(Some(seed)).measure(0).unwrap(); + let result_manual = tab_manual.fork(Some(seed)).measure(0).unwrap(); + assert_eq!(result_r, result_manual, "mismatch at seed {}", seed); + } + } + #[test] fn test_two_qubit_case() { let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-10); diff --git a/crates/ppvm-tableau/src/lib.rs b/crates/ppvm-tableau/src/lib.rs index 1738e7ac2..76f51a881 100644 --- a/crates/ppvm-tableau/src/lib.rs +++ b/crates/ppvm-tableau/src/lib.rs @@ -34,6 +34,8 @@ pub mod data; /// `Display` implementations for tableau types. pub mod display; +/// Pauli-string expectation values and pattern traces. +pub mod expectation; /// Gate implementations (Clifford, T, rotations). pub mod gates; /// Z-basis measurement, including loss-aware variants. diff --git a/crates/ppvm-traits/src/traits/branch/mod.rs b/crates/ppvm-traits/src/traits/branch/mod.rs index 0c50ddd43..774d6b38c 100644 --- a/crates/ppvm-traits/src/traits/branch/mod.rs +++ b/crates/ppvm-traits/src/traits/branch/mod.rs @@ -10,7 +10,7 @@ mod u3; pub use crx::CRx; pub use proj::Projection; -pub use rot1::RotationOne; +pub use rot1::{RotXY, RotationOne}; pub use rot2::RotationTwo; pub use tgate::TGate; pub use u3::U3Gate; diff --git a/crates/ppvm-traits/src/traits/branch/rot1.rs b/crates/ppvm-traits/src/traits/branch/rot1.rs index 27177a868..cf740d7b5 100644 --- a/crates/ppvm-traits/src/traits/branch/rot1.rs +++ b/crates/ppvm-traits/src/traits/branch/rot1.rs @@ -43,3 +43,13 @@ pub trait RotationOne { } } } + +/// Rotation about an axis in the x/y plane: +/// `R(axis_angle, θ) = exp(-i θ/2 · (cos(axis_angle)·X + sin(axis_angle)·Y))`. +/// +/// The in-plane axis is `X` rotated about `Z` by `axis_angle`, so +/// `R(axis_angle, θ) = RZ(axis_angle)·RX(θ)·RZ(−axis_angle)`. +pub trait RotXY { + /// `R(axis_angle, θ)` on qubit `addr0`. + fn r(&mut self, addr0: usize, axis_angle: T::Coeff, theta: T::Coeff); +} diff --git a/crates/ppvm-traits/src/traits/mod.rs b/crates/ppvm-traits/src/traits/mod.rs index 99ab548d1..57520129d 100644 --- a/crates/ppvm-traits/src/traits/mod.rs +++ b/crates/ppvm-traits/src/traits/mod.rs @@ -15,7 +15,7 @@ mod strategy; mod trace; mod word_trait; -pub use branch::{CRx, Projection, RotationOne, RotationTwo, TGate, U3Gate}; +pub use branch::{CRx, Projection, RotXY, RotationOne, RotationTwo, TGate, U3Gate}; pub use clifford::{Clifford, CliffordBatch, CliffordExtensions, CliffordExtensionsBatch}; pub use coefficient::{Coefficient, ComplexCoefficient}; pub use hash::HashFinalize; diff --git a/crates/ppvm-vihaco/Cargo.toml b/crates/ppvm-vihaco/Cargo.toml new file mode 100644 index 000000000..1f2a43b9f --- /dev/null +++ b/crates/ppvm-vihaco/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ppvm-vihaco" +version = "0.1.0" +edition = "2024" + +[features] +# Enables shot-level parallelism in `run_shots_parallel`/`run_shots`. +rayon = ["dep:rayon"] + +[dependencies] +bitvec = "1.0.1" +bnum = { version = "0.13.0", features = ["num-traits"] } +chumsky = "0.10.0" +eyre = "0.6.12" +log = "0.4.29" +num = "0.4.3" +rayon = { version = "1.10", optional = true } +ppvm-traits = { version = "0.1.0", path = "../ppvm-traits" } +ppvm-tableau = { version = "0.1.0", path = "../ppvm-tableau" } +smallvec = "1.15.1" +vihaco = "0.1.1" +vihaco-cpu = "0.1.1" +vihaco-parser = "0.1.1" +vihaco-parser-core = "0.1.1" +vihaco-circuit-isa = { version = "0.1.0", path = "../vihaco-circuit-isa" } +ppvm-pauli-sum = { version = "0.1.0", path = "../ppvm-pauli-sum" } + +# enable rayon if not on wasm +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ppvm-tableau = { version = "0.1.0", path = "../ppvm-tableau", features = ["rayon"] } diff --git a/crates/ppvm-vihaco/src/bytecode.rs b/crates/ppvm-vihaco/src/bytecode.rs new file mode 100644 index 000000000..ce77f788e --- /dev/null +++ b/crates/ppvm-vihaco/src/bytecode.rs @@ -0,0 +1,539 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Binary bytecode (`.ssb`) round-trip for PPVM modules. +//! +//! Serializes a resolved [`Module`] to a little-endian container — header +//! (magic + version + device info) → strings section → code section — and +//! reads it back. The per-instruction codec is the existing `WriteBytes` / +//! `FromBytes` generated for [`PPVMInstruction`]; this module only frames the +//! container around it. + +use std::io::{Read, Write}; + +use vihaco::instruction::{FromBytes, WriteBytes}; + +use crate::PPVMModule; +use crate::composite::{BackendKind, PPVM_MAGIC, PPVMDeviceInfo, PPVMInstruction}; + +/// Current `.ssb` format version. The reader rejects any other version. +pub const PPVM_BYTECODE_VERSION: u16 = 1; + +/// Byte length of the fixed portion of the header. The actual `header_size` +/// in the stream may exceed this when the optional `observable` string is +/// populated; the reader uses `header_size` to skip to the strings section. +/// +/// Field widths (bytes): magic(4) + version(2) + header_size(4) + n_qubits(4) +/// + coefficient_threshold(8) + backend(1) + max_pauli_weight_present(1) +/// + max_pauli_weight(8) + observable_present(1) = 33. +const FIXED_HEADER_SIZE: u32 = 4 + 2 + 4 + 4 + 8 + 1 + 1 + 8 + 1; + +/// Serialize a resolved module to the v1 `.ssb` byte stream. +pub fn write_module(module: &PPVMModule, w: &mut W) -> eyre::Result<()> { + // v1 serializes only code, strings, and device info. Refuse to silently + // drop any table a future feature might populate. + let populated = if !module.functions.is_empty() { + Some("functions") + } else if !module.labels.is_empty() { + Some("labels") + } else if !module.constants.is_empty() { + Some("constants") + } else if !module.source_symbols.is_empty() { + Some("source_symbols") + } else if module.main_function.is_some() { + Some("main_function") + } else if module.file != 0 { + Some("file") + } else { + None + }; + if let Some(table) = populated { + return Err(eyre::eyre!( + "bytecode v1 cannot represent a populated `{table}`" + )); + } + + let info = &module.extra; + let n_qubits = u32::try_from(info.n_qubits) + .map_err(|_| eyre::eyre!("n_qubits {} does not fit in u32", info.n_qubits))?; + + // The header is `FIXED_HEADER_SIZE` bytes plus, when an observable is + // present, a u32 length followed by its UTF-8 bytes. + let observable_bytes: &[u8] = info + .observable + .as_ref() + .map(String::as_bytes) + .unwrap_or(&[]); + let observable_present: u8 = u8::from(info.observable.is_some()); + let observable_len = u32::try_from(observable_bytes.len()).map_err(|_| { + eyre::eyre!( + "observable length {} does not fit in u32", + observable_bytes.len() + ) + })?; + let observable_trailer: u32 = if info.observable.is_some() { + 4 + observable_len + } else { + 0 + }; + let header_size = FIXED_HEADER_SIZE + observable_trailer; + + // Header. + w.write_all(&PPVM_MAGIC.to_le_bytes())?; + w.write_all(&PPVM_BYTECODE_VERSION.to_le_bytes())?; + w.write_all(&header_size.to_le_bytes())?; + w.write_all(&n_qubits.to_le_bytes())?; + w.write_all(&info.coefficient_threshold.to_le_bytes())?; + w.write_all(&[backend_to_u8(info.backend)])?; + let (mpw_present, mpw_value) = match info.max_pauli_weight { + Some(w) => ( + 1u8, + u64::try_from(w) + .map_err(|_| eyre::eyre!("max_pauli_weight {} does not fit in u64", w))?, + ), + None => (0u8, 0u64), + }; + w.write_all(&[mpw_present])?; + w.write_all(&mpw_value.to_le_bytes())?; + w.write_all(&[observable_present])?; + if info.observable.is_some() { + w.write_all(&observable_len.to_le_bytes())?; + w.write_all(observable_bytes)?; + } + + // Strings section: count, then each entry as len-prefixed UTF-8. + let string_count = + u32::try_from(module.strings.len()).map_err(|_| eyre::eyre!("string count exceeds u32"))?; + w.write_all(&string_count.to_le_bytes())?; + for s in &module.strings { + let len = u32::try_from(s.len()).map_err(|_| eyre::eyre!("string length exceeds u32"))?; + w.write_all(&len.to_le_bytes())?; + w.write_all(s.as_bytes())?; + } + + // Code section: count, then each instruction's fixed-width frame. + let code_count = + u32::try_from(module.code.len()).map_err(|_| eyre::eyre!("code length exceeds u32"))?; + w.write_all(&code_count.to_le_bytes())?; + for inst in &module.code { + inst.write_bytes(w)?; + } + + Ok(()) +} + +/// Reconstruct a module from a v1 `.ssb` byte stream. +pub fn read_module(r: &mut R) -> eyre::Result { + // Header. + let magic = read_u32(r)?; + if magic != PPVM_MAGIC { + return Err(eyre::eyre!( + "not a PPVM bytecode file (magic 0x{magic:08X})" + )); + } + let version = read_u16(r)?; + if version != PPVM_BYTECODE_VERSION { + return Err(eyre::eyre!("unsupported bytecode version {version}")); + } + let header_size = read_u32(r)?; + let n_qubits = read_u32(r)? as usize; + let coefficient_threshold = read_f64(r)?; + let backend = backend_from_u8(read_u8(r)?)?; + let mpw_present = read_u8(r)?; + let mpw_value = read_u64(r)?; + let max_pauli_weight = match mpw_present { + 0 => None, + 1 => Some(usize::try_from(mpw_value).map_err(|_| { + eyre::eyre!("max_pauli_weight {mpw_value} does not fit in usize on this platform") + })?), + other => { + return Err(eyre::eyre!( + "invalid max_pauli_weight presence byte {other}" + )); + } + }; + let observable_present = read_u8(r)?; + let observable = match observable_present { + 0 => None, + 1 => { + let len = read_u32(r)? as usize; + let mut bytes = vec![0u8; len]; + r.read_exact(&mut bytes)?; + Some(String::from_utf8(bytes)?) + } + other => { + return Err(eyre::eyre!("invalid observable presence byte {other}")); + } + }; + + // Sections begin at `header_size`; skip any header bytes beyond what this + // reader knows about (forward compat / self-description). + let consumed = FIXED_HEADER_SIZE + + if observable.is_some() { + 4 + u32::try_from(observable.as_deref().unwrap().len()) + .map_err(|_| eyre::eyre!("observable length does not fit in u32"))? + } else { + 0 + }; + if header_size < consumed { + return Err(eyre::eyre!( + "header_size {header_size} smaller than the {consumed} bytes already consumed" + )); + } + skip_bytes(r, u64::from(header_size - consumed))?; + + // Don't pre-allocate from an untrusted count; grow as entries are read. + let string_count = read_u32(r)?; + let mut strings = Vec::new(); + for _ in 0..string_count { + let len = read_u32(r)? as usize; + let mut bytes = vec![0u8; len]; + r.read_exact(&mut bytes)?; + strings.push(String::from_utf8(bytes)?); + } + + let code_count = read_u32(r)?; + let mut code = Vec::new(); + for _ in 0..code_count { + code.push(PPVMInstruction::from_bytes(r)?); + } + + Ok(PPVMModule { + extra: PPVMDeviceInfo { + magic, + n_qubits, + coefficient_threshold, + backend, + observable, + max_pauli_weight, + }, + strings, + code, + ..Default::default() + }) +} + +fn backend_to_u8(backend: BackendKind) -> u8 { + match backend { + BackendKind::Tableau => 0, + BackendKind::PauliSum => 1, + BackendKind::LossyPauliSum => 2, + } +} + +fn backend_from_u8(byte: u8) -> eyre::Result { + match byte { + 0 => Ok(BackendKind::Tableau), + 1 => Ok(BackendKind::PauliSum), + 2 => Ok(BackendKind::LossyPauliSum), + other => Err(eyre::eyre!("invalid backend tag {other}")), + } +} + +/// Serialize a module to an owned byte vector. +pub fn module_to_bytes(module: &PPVMModule) -> eyre::Result> { + let mut buf = Vec::new(); + write_module(module, &mut buf)?; + Ok(buf) +} + +/// Reconstruct a module from a byte slice. +pub fn module_from_bytes(bytes: &[u8]) -> eyre::Result { + read_module(&mut &bytes[..]) +} + +/// Cheap sniff: does this byte stream begin with the PPVM `.ssb` magic? +/// +/// Reads the leading four bytes the same way [`read_module`] does — as a +/// little-endian `u32` — so a positive result here means [`read_module`] will +/// accept the magic. A stream shorter than the magic is not bytecode. +pub fn is_bytecode(bytes: &[u8]) -> bool { + bytes.len() >= 4 && u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) == PPVM_MAGIC +} + +/// "Dump": compile `.sst` source straight to the `.ssb` byte stream. +pub fn compile_to_bytes(source: &str) -> eyre::Result> { + let module = crate::compile_program(source)?; + module_to_bytes(&module) +} + +fn read_u8(r: &mut R) -> eyre::Result { + let mut b = [0u8; 1]; + r.read_exact(&mut b)?; + Ok(b[0]) +} + +fn read_u16(r: &mut R) -> eyre::Result { + let mut b = [0u8; 2]; + r.read_exact(&mut b)?; + Ok(u16::from_le_bytes(b)) +} + +fn read_u32(r: &mut R) -> eyre::Result { + let mut b = [0u8; 4]; + r.read_exact(&mut b)?; + Ok(u32::from_le_bytes(b)) +} + +fn read_u64(r: &mut R) -> eyre::Result { + let mut b = [0u8; 8]; + r.read_exact(&mut b)?; + Ok(u64::from_le_bytes(b)) +} + +fn read_f64(r: &mut R) -> eyre::Result { + let mut b = [0u8; 8]; + r.read_exact(&mut b)?; + Ok(f64::from_le_bytes(b)) +} + +fn skip_bytes(r: &mut R, n: u64) -> eyre::Result<()> { + let skipped = std::io::copy(&mut r.take(n), &mut std::io::sink())?; + if skipped != n { + return Err(eyre::eyre!("unexpected EOF skipping {n} header bytes")); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use vihaco::Value; + + use super::*; + + fn empty_module() -> PPVMModule { + PPVMModule::default() + } + + #[test] + fn round_trips_device_info() { + let mut m = empty_module(); + m.extra.n_qubits = 7; + m.extra.coefficient_threshold = 1e-9; + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + let back = read_module(&mut buf.as_slice()).unwrap(); + + assert_eq!(back, m); + } + + #[test] + fn round_trips_paulisum_device_info() { + let mut m = empty_module(); + m.extra.n_qubits = 6; + m.extra.backend = BackendKind::PauliSum; + m.extra.observable = Some("ZZIIII".to_string()); + m.extra.max_pauli_weight = Some(8); + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + let back = read_module(&mut buf.as_slice()).unwrap(); + + assert_eq!(back, m); + } + + #[test] + fn round_trips_lossy_backend_without_observable() { + let mut m = empty_module(); + m.extra.n_qubits = 4; + m.extra.backend = BackendKind::LossyPauliSum; + // observable and max_pauli_weight stay None — verifies the absent path. + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + let back = read_module(&mut buf.as_slice()).unwrap(); + + assert_eq!(back, m); + assert_eq!(back.extra.observable, None); + assert_eq!(back.extra.max_pauli_weight, None); + } + + #[test] + fn round_trips_code() { + use vihaco_circuit_isa::CircuitInstruction; + use vihaco_cpu::Instruction as Cpu; + + let mut m = empty_module(); + m.extra.n_qubits = 2; + m.code = vec![ + PPVMInstruction::Cpu(Cpu::Const(Value::U64(0))), + PPVMInstruction::Circuit(CircuitInstruction::H), + PPVMInstruction::Circuit(CircuitInstruction::R), + PPVMInstruction::Cpu(Cpu::Branch(1)), + PPVMInstruction::Cpu(Cpu::ConditionalBranch(0, 1)), + PPVMInstruction::Cpu(Cpu::Call(0, 1)), + PPVMInstruction::Cpu(Cpu::Return(0)), + ]; + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + let back = read_module(&mut buf.as_slice()).unwrap(); + + assert_eq!(back, m); + } + + #[test] + fn read_honors_header_size_with_padding() { + let mut m = empty_module(); + m.extra.n_qubits = 3; + m.strings = vec!["hi".to_string()]; + m.code = vec![PPVMInstruction::Cpu(vihaco_cpu::Instruction::Return(0))]; + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + + // Simulate a larger header: 4 padding bytes after the fixed fields, + // with header_size bumped to match. The reader must skip to it. + // (This test uses an empty observable, so the on-disk header size + // equals FIXED_HEADER_SIZE.) + buf[6..10].copy_from_slice(&(FIXED_HEADER_SIZE + 4).to_le_bytes()); + for i in 0..4 { + buf.insert(FIXED_HEADER_SIZE as usize + i, 0x00); + } + + let back = read_module(&mut buf.as_slice()).unwrap(); + assert_eq!(back, m); + } + + #[test] + fn compile_to_bytes_round_trips_through_resolve() { + let src = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + const.u64 0\n\ + const.u64 1\n\ + circuit cnot\n\ + ret\n\ + }\n"; + + let bytes = compile_to_bytes(src).unwrap(); + let back = module_from_bytes(&bytes).unwrap(); + let expected = crate::compile_program(src).unwrap(); + + assert_eq!(back, expected); + } + + #[test] + fn loaded_bytecode_executes_like_text() { + let src = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n circuit h\n\ + const.u64 0\n const.u64 1\n circuit cnot\n\ + const.u64 0\n circuit measure\n\ + const.u64 1\n circuit measure\n\ + ret\n }\n"; + let bytes = compile_to_bytes(src).unwrap(); + + let mut machine = crate::composite::PPVM::default(); + machine.load_bytecode(&bytes).unwrap(); + machine.run().unwrap(); + + assert_eq!(machine.measurement_record().len(), 2); + } + + #[test] + fn load_bytecode_file_reads_from_disk() { + let src = "device circuit.n_qubits 1;\n\ + fn @main() { const.u64 0\n circuit measure\n ret }\n"; + let bytes = compile_to_bytes(src).unwrap(); + let path = std::env::temp_dir().join("ppvm_load_bytecode_file_test.ssb"); + std::fs::write(&path, &bytes).unwrap(); + + let mut machine = crate::composite::PPVM::default(); + machine.load_bytecode_file(path.to_str().unwrap()).unwrap(); + machine.run().unwrap(); + + assert_eq!(machine.measurement_record().len(), 1); + let _ = std::fs::remove_file(&path); + } + + #[test] + fn read_rejects_truncated_input() { + let mut m = empty_module(); + m.extra.n_qubits = 2; + m.code = vec![PPVMInstruction::Cpu(vihaco_cpu::Instruction::Return(0))]; + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + buf.truncate(buf.len() - 3); // cut off mid-instruction + + assert!(read_module(&mut buf.as_slice()).is_err()); + } + + #[test] + fn write_rejects_populated_functions_table() { + use vihaco::module::{FunctionInfo, Signature}; + + let mut m = empty_module(); + m.extra.n_qubits = 1; + m.functions.push(FunctionInfo { + name: 0, + signature: Signature { + params: vec![], + ret: vec![], + }, + local_count: 0, + start_address: 0, + end_address: 0, + file: 0, + }); + + let mut buf = Vec::new(); + let err = write_module(&m, &mut buf).unwrap_err(); + assert!(err.to_string().contains("functions"), "err: {err}"); + } + + #[test] + fn read_rejects_bad_magic() { + let mut m = empty_module(); + m.extra.n_qubits = 2; + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + // Corrupt the magic (first 4 bytes). + buf[0] ^= 0xFF; + + let err = read_module(&mut buf.as_slice()).unwrap_err(); + assert!( + err.to_string().contains("not a PPVM bytecode file"), + "err: {err}" + ); + } + + #[test] + fn round_trips_strings() { + let mut m = empty_module(); + m.extra.n_qubits = 1; + m.strings = vec![ + String::new(), + "hello".to_string(), + "tab\tnl\nquote\"".to_string(), + "üñîçødé ⚛".to_string(), + ]; + + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + let back = read_module(&mut buf.as_slice()).unwrap(); + + assert_eq!(back, m); + } + + #[test] + fn read_rejects_unsupported_version() { + let mut m = empty_module(); + m.extra.n_qubits = 2; + let mut buf = Vec::new(); + write_module(&m, &mut buf).unwrap(); + // Bump the version (bytes 4..6) past the supported one. + let bad = (PPVM_BYTECODE_VERSION + 1).to_le_bytes(); + buf[4] = bad[0]; + buf[5] = bad[1]; + + let err = read_module(&mut buf.as_slice()).unwrap_err(); + assert!( + err.to_string().contains("unsupported bytecode version"), + "err: {err}" + ); + } +} diff --git a/crates/ppvm-vihaco/src/component.rs b/crates/ppvm-vihaco/src/component.rs new file mode 100644 index 000000000..f6147908f --- /dev/null +++ b/crates/ppvm-vihaco/src/component.rs @@ -0,0 +1,927 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use crate::composite::PPVMDeviceInfo; +use crate::measurements::{ + CircuitOutcomeEffect, MeasurementEffect, MeasurementOutcome, TraceEffect, +}; +use bitvec::view::BitView; +use bnum::types::{U256, U512, U1024, U2048}; +use eyre::{Result, eyre}; +use num::PrimInt; +use num::complex::Complex64; +use ppvm_pauli_sum::config::fx64hash::Byte8F64; +use ppvm_pauli_sum::config::indexmap::ByteFxHashF64; +use ppvm_pauli_sum::strategy::{CoefficientThreshold, CombinedStrategy, MaxPauliWeight}; +use ppvm_tableau::prelude::*; +use vihaco::{Effects, component, observe}; +use vihaco_circuit_isa::{CircuitEffect, CircuitInstruction, CircuitMessage}; + +/// Truncation strategy used by every `PauliSum` / `LossyPauliSum` size bucket. +/// Coefficient-threshold pruning is always on; the Pauli-weight cap is set per +/// run from the header (defaults to `usize::MAX` = no cap). +type PauliSumStrategy = CombinedStrategy; + +/// `PauliSum`'s `T` for the lossless backend: `[u8; N]` storage, fx hash, +/// f64 coefficients, the strategy above. +type PauliSumConfig = ByteFxHashF64; + +/// Same as `PauliSumConfig` but with `LossyPauliWord` as the word type, so the +/// loss-channel methods are dispatchable on the resulting `PauliSum`. +/// `LossyPauliWord`'s second type parameter (hasher) defaults to +/// `fxhash::FxBuildHasher`, matching `ByteFxHashF64`'s internal hasher. +type LossyPauliSumConfig = + ByteFxHashF64>; + +/// Build a `PauliSumStrategy` value from a `PPVMDeviceInfo`. Pulled out so the +/// six size-bucket constructors don't each repeat the strategy spelling. +fn paulisum_strategy(info: &PPVMDeviceInfo) -> PauliSumStrategy { + CombinedStrategy( + CoefficientThreshold(info.coefficient_threshold), + MaxPauliWeight(info.max_pauli_weight.unwrap_or(usize::MAX)), + ) +} + +macro_rules! batch_for { + ($tab:expr, $method:ident, $addrs:expr) => { + for addr in $addrs { $tab.$method(*addr); } + }; + ($tab:expr, $method:ident, $addrs:expr, $($arg:expr),+) => { + for addr in $addrs { $tab.$method(*addr, $($arg),+); } + }; +} + +/// Two-qubit sibling of [`batch_for!`]: drives a method that takes two qubit +/// addresses (plus optional extra args) over a slice of `(usize, usize)` pairs. +macro_rules! batch_pairs_for { + ($state:expr, $method:ident, $pairs:expr) => { + for &(a, b) in $pairs { $state.$method(a, b); } + }; + ($state:expr, $method:ident, $pairs:expr, $($arg:expr),+) => { + for &(a, b) in $pairs { $state.$method(a, b, $($arg),+); } + }; +} + +pub struct CircuitExecutor, I: TableauIndex, C: SparseVector> { + pub tab: GeneralizedTableau, +} + +#[component(instruction = CircuitInstruction, message = CircuitMessage, effect = CircuitOutcomeEffect)] +impl CircuitExecutor +where + T: Config, + <::Storage as BitView>::Store: PrimInt, + I: TableauIndex + Send + Sync + std::fmt::Debug, + C: SparseVector + std::fmt::Debug, +{ + fn execute( + &mut self, + inst: CircuitInstruction, + msg: CircuitMessage, + ) -> Result> { + self.execute_instruction(&inst, &msg) + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + use CircuitInstruction::*; + use CircuitMessage::*; + + match (inst, msg) { + // Single-qubit Clifford + (X, &Qubit(addr)) => self.tab.x(addr), + (Y, &Qubit(addr)) => self.tab.y(addr), + (Z, &Qubit(addr)) => self.tab.z(addr), + (H, &Qubit(addr)) => self.tab.h(addr), + (S, &Qubit(addr)) => self.tab.s(addr), + (SAdj, &Qubit(addr)) => self.tab.s_dag(addr), + (SqrtX, &Qubit(addr)) => self.tab.sqrt_x(addr), + (SqrtY, &Qubit(addr)) => self.tab.sqrt_y(addr), + (SqrtXAdj, &Qubit(addr)) => self.tab.sqrt_x_dag(addr), + (SqrtYAdj, &Qubit(addr)) => self.tab.sqrt_y_dag(addr), + + // Controlled gates + (CNOT, &TwoQubit(addr0, addr1)) => self.tab.cnot(addr0, addr1), + (CZ, &TwoQubit(addr0, addr1)) => self.tab.cz(addr0, addr1), + + // T gate + (T, &Qubit(addr)) => self.tab.t(addr), + (TAdj, &Qubit(addr)) => self.tab.t_dag(addr), + + // Single-qubit rotations + (RX, &QubitAndFloat(addr, angle)) => self.tab.rx(addr, angle), + (RY, &QubitAndFloat(addr, angle)) => self.tab.ry(addr, angle), + (RZ, &QubitAndFloat(addr, angle)) => self.tab.rz(addr, angle), + + // Two-qubit rotations + (RXX, &TwoQubitAndFloat(addr0, addr1, angle)) => self.tab.rxx(addr0, addr1, angle), + (RYY, &TwoQubitAndFloat(addr0, addr1, angle)) => self.tab.ryy(addr0, addr1, angle), + (RZZ, &TwoQubitAndFloat(addr0, addr1, angle)) => self.tab.rzz(addr0, addr1, angle), + + // U3 + (U3, &QubitU3(addr, theta, phi, lam)) => self.tab.u3(addr, theta, phi, lam), + + // RXY: rotation about an axis in the x/y plane + (R, &QubitAndTwoFloats(addr, axis_angle, theta)) => self.tab.r(addr, axis_angle, theta), + + // Measure & Reset + (Measure, &Qubit(addr)) => { + let outcome: MeasurementOutcome = self.tab.measure(addr).into(); + return Ok(Effects::one(CircuitOutcomeEffect::Measurement( + MeasurementEffect { + measurement_results: smallvec::smallvec![outcome], + }, + ))); + } + (Reset, &Qubit(addr)) => self.tab.reset(addr), + + // Noise + (Depolarize, &QubitAndFloat(addr, p)) => self.tab.depolarize1(addr, p), + (Depolarize2, &TwoQubitAndFloat(addr0, addr1, p)) => { + self.tab.depolarize2(addr0, addr1, p) + } + (PauliError, QubitAndFloatArr3(addr0, ps)) => self.tab.pauli_error(*addr0, *ps), + (TwoQubitPauliError, TwoQubitAndFloatArr15(addr0, addr1, ps)) => { + self.tab.two_qubit_pauli_error(*addr0, *addr1, *ps) + } + + // Loss + (Loss, &QubitAndFloat(addr, p)) => self.tab.loss_channel(addr, p), + (CorrelatedLoss, TwoQubitAndFloatArr3(addr0, addr1, ps)) => { + self.tab.correlated_loss_channel(*addr0, *addr1, *ps) + } + + /* BATCH OPERATIONS START HERE */ + // Batch: dedicated batch methods + (SqrtX, QubitBatch(addrs)) => self.tab.sqrt_x_many(addrs), + (SqrtY, QubitBatch(addrs)) => self.tab.sqrt_y_many(addrs), + (SqrtXAdj, QubitBatch(addrs)) => self.tab.sqrt_x_dag_many(addrs), + (SqrtYAdj, QubitBatch(addrs)) => self.tab.sqrt_y_dag_many(addrs), + (H, QubitBatch(addrs)) => self.tab.h_many(addrs), + (CZ, TwoQubitBatch(pairs)) => self.tab.cz_many(pairs), + + // TODO: replace things below by actual batched methods once they are available + // Batch: single-qubit for loops + (X, QubitBatch(addrs)) => batch_for!(self.tab, x, addrs), + (Y, QubitBatch(addrs)) => batch_for!(self.tab, y, addrs), + (Z, QubitBatch(addrs)) => batch_for!(self.tab, z, addrs), + (S, QubitBatch(addrs)) => batch_for!(self.tab, s, addrs), + (SAdj, QubitBatch(addrs)) => batch_for!(self.tab, s_dag, addrs), + (T, QubitBatch(addrs)) => batch_for!(self.tab, t, addrs), + (TAdj, QubitBatch(addrs)) => batch_for!(self.tab, t_dag, addrs), + (Reset, QubitBatch(addrs)) => batch_for!(self.tab, reset, addrs), + (RX, QubitBatchAndFloat(addrs, angle)) => batch_for!(self.tab, rx, addrs, *angle), + (RY, QubitBatchAndFloat(addrs, angle)) => batch_for!(self.tab, ry, addrs, *angle), + (RZ, QubitBatchAndFloat(addrs, angle)) => batch_for!(self.tab, rz, addrs, *angle), + (Depolarize, QubitBatchAndFloat(addrs, p)) => { + batch_for!(self.tab, depolarize1, addrs, *p) + } + (Loss, QubitBatchAndFloat(addrs, p)) => batch_for!(self.tab, loss_channel, addrs, *p), + (PauliError, QubitBatchAndFloatArr3(addrs, ps)) => { + batch_for!(self.tab, pauli_error, addrs, *ps) + } + (U3, QubitBatchU3(addrs, theta, phi, lam)) => { + batch_for!(self.tab, u3, addrs, *theta, *phi, *lam) + } + + // Batch: two-qubit for loops + (CNOT, TwoQubitBatch(pairs)) => { + for &(a, b) in pairs { + self.tab.cnot(a, b); + } + } + (RXX, TwoQubitBatchAndFloat(pairs, angle)) => { + for &(a, b) in pairs { + self.tab.rxx(a, b, *angle); + } + } + (RYY, TwoQubitBatchAndFloat(pairs, angle)) => { + for &(a, b) in pairs { + self.tab.ryy(a, b, *angle); + } + } + (RZZ, TwoQubitBatchAndFloat(pairs, angle)) => { + for &(a, b) in pairs { + self.tab.rzz(a, b, *angle); + } + } + (Depolarize2, TwoQubitBatchAndFloat(pairs, p)) => { + for &(a, b) in pairs { + self.tab.depolarize2(a, b, *p); + } + } + (TwoQubitPauliError, TwoQubitBatchAndFloatArr15(pairs, ps)) => { + for &(a, b) in pairs { + self.tab.two_qubit_pauli_error(a, b, *ps); + } + } + (CorrelatedLoss, TwoQubitBatchAndFloatArr3(pairs, ps)) => { + for &(a, b) in pairs { + self.tab.correlated_loss_channel(a, b, *ps); + } + } + + // Batch: measure (emits per qubit) + (Measure, QubitBatch(addrs)) => { + let outcomes = addrs.iter().map(|&addr| self.tab.measure(addr).into()); + return Ok(Effects::one(CircuitOutcomeEffect::Measurement( + MeasurementEffect { + measurement_results: outcomes.collect(), + }, + ))); + } + + // Truncate is a silent no-op on the Tableau backend — the tableau's + // gate methods already prune via the configured coefficient + // threshold, so there's nothing extra to do here. + (Truncate, None) => {} + + // Trace: parse the resolved pattern and compute Σ_{P matches} ⟨ψ|P|ψ⟩ + // on the tableau state. Asymmetric with the PauliSum semantics by + // design (Decision 9): on the tableau this is a sum of expectations, + // not a coefficient filter. + (Trace, PauliPatternStr(s)) => { + let pat = PauliPattern::parse(s) + .map_err(|e| eyre!("invalid Pauli pattern `{s}`: {e:?}"))?; + let value = self.tab.trace(&pat); + return Ok(Effects::one(CircuitOutcomeEffect::Trace(TraceEffect { + value, + }))); + } + + // Fallback + (inst, msg) => { + return Err(eyre!( + "Invalid circuit instruction arguments {:?} for instruction {:?}", + msg, + inst + )); + } + }; + + Ok(Effects::None) + } +} + +impl vihaco::Reset for CircuitExecutor +where + T: Config, + <::Storage as BitView>::Store: PrimInt, + I: TableauIndex + Send + Sync + std::fmt::Debug, + C: SparseVector + std::fmt::Debug, +{ + fn reset(&mut self) { + self.tab.reset_all(); + } +} + +/// Shared dispatch body for `PauliSumExecutor` and `LossyPauliSumExecutor`. +/// Every non-loss `CircuitInstruction` lands here. `LossyPauliSumExecutor` +/// matches `Loss` / `CorrelatedLoss` (single + batched) before invoking this +/// macro and never reaches the loss-rejection arm below. +/// +/// `$self` is passed as an `ident` (typically `self`) so the macro's +/// expansion shares hygiene with the surrounding method's `self` parameter. +/// `$inst` / `$msg` are passed the same way; `$backend` is the human-readable +/// backend name baked into error messages. +macro_rules! dispatch_common_paulisum { + ($self:ident, $inst:ident, $msg:ident, $backend:literal) => {{ + use CircuitInstruction::*; + use CircuitMessage::*; + match ($inst, $msg) { + // Single-qubit Clifford + (X, &Qubit(addr)) => $self.state.x(addr), + (Y, &Qubit(addr)) => $self.state.y(addr), + (Z, &Qubit(addr)) => $self.state.z(addr), + (H, &Qubit(addr)) => $self.state.h(addr), + (S, &Qubit(addr)) => $self.state.s(addr), + (SAdj, &Qubit(addr)) => $self.state.s_dag(addr), + (SqrtX, &Qubit(addr)) => $self.state.sqrt_x(addr), + (SqrtY, &Qubit(addr)) => $self.state.sqrt_y(addr), + (SqrtXAdj, &Qubit(addr)) => $self.state.sqrt_x_dag(addr), + (SqrtYAdj, &Qubit(addr)) => $self.state.sqrt_y_dag(addr), + + // Controlled gates + (CNOT, &TwoQubit(addr0, addr1)) => $self.state.cnot(addr0, addr1), + (CZ, &TwoQubit(addr0, addr1)) => $self.state.cz(addr0, addr1), + + // Single-qubit rotations + (RX, &QubitAndFloat(addr, angle)) => $self.state.rx(addr, angle), + (RY, &QubitAndFloat(addr, angle)) => $self.state.ry(addr, angle), + (RZ, &QubitAndFloat(addr, angle)) => $self.state.rz(addr, angle), + + // Two-qubit rotations + (RXX, &TwoQubitAndFloat(addr0, addr1, angle)) => { + $self.state.rxx(addr0, addr1, angle) + } + (RYY, &TwoQubitAndFloat(addr0, addr1, angle)) => { + $self.state.ryy(addr0, addr1, angle) + } + (RZZ, &TwoQubitAndFloat(addr0, addr1, angle)) => { + $self.state.rzz(addr0, addr1, angle) + } + + // RXY: rotation about an axis in the x/y plane + (R, &QubitAndTwoFloats(addr, axis_angle, theta)) => { + $self.state.r(addr, axis_angle, theta) + } + + // Noise + (Depolarize, &QubitAndFloat(addr, p)) => $self.state.depolarize1(addr, p), + (Depolarize2, &TwoQubitAndFloat(addr0, addr1, p)) => { + $self.state.depolarize2(addr0, addr1, p) + } + (PauliError, QubitAndFloatArr3(addr0, ps)) => { + $self.state.pauli_error(*addr0, *ps) + } + (TwoQubitPauliError, TwoQubitAndFloatArr15(addr0, addr1, ps)) => { + $self.state.two_qubit_pauli_error(*addr0, *addr1, *ps) + } + + // Truncate: pruning per the configured strategy. + (Truncate, None) => $self.state.truncate(), + + // Batched arms: simple for-loop dispatch (no dedicated batch + // methods on PauliSum, unlike GeneralizedTableau). + (X, QubitBatch(addrs)) => batch_for!($self.state, x, addrs), + (Y, QubitBatch(addrs)) => batch_for!($self.state, y, addrs), + (Z, QubitBatch(addrs)) => batch_for!($self.state, z, addrs), + (H, QubitBatch(addrs)) => batch_for!($self.state, h, addrs), + (S, QubitBatch(addrs)) => batch_for!($self.state, s, addrs), + (SAdj, QubitBatch(addrs)) => batch_for!($self.state, s_dag, addrs), + (SqrtX, QubitBatch(addrs)) => batch_for!($self.state, sqrt_x, addrs), + (SqrtY, QubitBatch(addrs)) => batch_for!($self.state, sqrt_y, addrs), + (SqrtXAdj, QubitBatch(addrs)) => batch_for!($self.state, sqrt_x_dag, addrs), + (SqrtYAdj, QubitBatch(addrs)) => batch_for!($self.state, sqrt_y_dag, addrs), + (RX, QubitBatchAndFloat(addrs, angle)) => { + batch_for!($self.state, rx, addrs, *angle) + } + (RY, QubitBatchAndFloat(addrs, angle)) => { + batch_for!($self.state, ry, addrs, *angle) + } + (RZ, QubitBatchAndFloat(addrs, angle)) => { + batch_for!($self.state, rz, addrs, *angle) + } + (Depolarize, QubitBatchAndFloat(addrs, p)) => { + batch_for!($self.state, depolarize1, addrs, *p) + } + (PauliError, QubitBatchAndFloatArr3(addrs, ps)) => { + batch_for!($self.state, pauli_error, addrs, *ps) + } + (CNOT, TwoQubitBatch(pairs)) => batch_pairs_for!($self.state, cnot, pairs), + (CZ, TwoQubitBatch(pairs)) => batch_pairs_for!($self.state, cz, pairs), + (RXX, TwoQubitBatchAndFloat(pairs, angle)) => { + batch_pairs_for!($self.state, rxx, pairs, *angle) + } + (RYY, TwoQubitBatchAndFloat(pairs, angle)) => { + batch_pairs_for!($self.state, ryy, pairs, *angle) + } + (RZZ, TwoQubitBatchAndFloat(pairs, angle)) => { + batch_pairs_for!($self.state, rzz, pairs, *angle) + } + (Depolarize2, TwoQubitBatchAndFloat(pairs, p)) => { + batch_pairs_for!($self.state, depolarize2, pairs, *p) + } + (TwoQubitPauliError, TwoQubitBatchAndFloatArr15(pairs, ps)) => { + batch_pairs_for!($self.state, two_qubit_pauli_error, pairs, *ps) + } + + // Not supported on either backend (Decision 11 + Gate Support + // Matrix). Loss / CorrelatedLoss handling differs by backend + // and lives in the caller's impl block, not this macro. + (Measure | Reset, _) => { + return Err(eyre!("{} is not supported on the {} backend", $inst, $backend)); + } + + // T / T_dag / U3 are listed as supported on PauliSum in the + // plan's Gate Support Matrix, but ppvm-runtime does not yet + // implement TGate or U3Gate for PauliSum (only for + // GeneralizedTableau). + (T | TAdj | U3, _) => { + return Err(eyre!( + "{} on {} requires upstream ppvm-runtime support that is not yet implemented", + $inst, + $backend + )); + } + + // Trace: parse the resolved pattern string and compute the + // trace. Per plan Decision 9, parsing happens on every + // execution; no module-load caching. + (Trace, PauliPatternStr(s)) => { + let pat = PauliPattern::parse(s) + .map_err(|e| eyre!("invalid Pauli pattern `{}`: {:?}", s, e))?; + let value = $self.state.trace(&pat); + return Ok(Effects::one(CircuitOutcomeEffect::Trace(TraceEffect { + value, + }))); + } + + // Fallback (mismatched shapes, etc.) + (inst, msg) => { + return Err(eyre!( + "Invalid circuit instruction arguments {:?} for instruction {:?} on the {} backend", + msg, + inst, + $backend + )); + } + }; + Ok(Effects::None) + }}; +} + +/// PauliSum-backed executor (Heisenberg picture). Holds a `PauliSum` and +/// answers the same `CircuitInstruction` vocabulary as `CircuitExecutor`, +/// but without measurement / reset / loss support. +pub struct PauliSumExecutor> { + pub state: PauliSum, +} + +#[component(instruction = CircuitInstruction, message = CircuitMessage, effect = CircuitOutcomeEffect)] +impl PauliSumExecutor +where + T: Config, + for<'a> PauliSum: Trace<'a, PauliPattern, Output = f64>, +{ + fn execute( + &mut self, + inst: CircuitInstruction, + msg: CircuitMessage, + ) -> Result> { + self.execute_instruction(&inst, &msg) + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + use CircuitInstruction::*; + + if matches!(inst, Loss | CorrelatedLoss) { + return Err(eyre!( + "{inst} is not supported on the PauliSum backend; use the LossyPauliSum backend instead" + )); + } + + dispatch_common_paulisum!(self, inst, msg, "PauliSum") + } +} + +impl vihaco::Reset for PauliSumExecutor +where + T: Config, +{ + fn reset(&mut self) { + // TODO(Task 5/6): rebuild self.state from the seeded observable. + } +} + +/// LossyPauliSum-backed executor. Same dispatch as `PauliSumExecutor` plus +/// `Loss` / `CorrelatedLoss` channels. The concrete `T` used by the +/// enclosing `Circuit::LossyPauliSum` variant is a `Config` whose +/// `PauliWordType` is `LossyPauliWord` (see `LossyPauliSumConfig`). +pub struct LossyPauliSumExecutor> { + pub state: PauliSum, +} + +#[component(instruction = CircuitInstruction, message = CircuitMessage, effect = CircuitOutcomeEffect)] +impl LossyPauliSumExecutor +where + T: Config, + for<'a> PauliSum: Trace<'a, PauliPattern, Output = f64>, +{ + fn execute( + &mut self, + inst: CircuitInstruction, + msg: CircuitMessage, + ) -> Result> { + self.execute_instruction(&inst, &msg) + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + use CircuitInstruction::*; + use CircuitMessage::*; + + // Loss / CorrelatedLoss are the only instructions that differ from + // PauliSum; handle them here then delegate everything else to the + // shared dispatch. + match (inst, msg) { + (Loss, &QubitAndFloat(addr, p)) => { + self.state.loss_channel(addr, p); + return Ok(Effects::None); + } + (CorrelatedLoss, TwoQubitAndFloatArr3(addr0, addr1, ps)) => { + self.state.correlated_loss_channel(*addr0, *addr1, *ps); + return Ok(Effects::None); + } + (Loss, QubitBatchAndFloat(addrs, p)) => { + batch_for!(self.state, loss_channel, addrs, *p); + return Ok(Effects::None); + } + (CorrelatedLoss, TwoQubitBatchAndFloatArr3(pairs, ps)) => { + batch_pairs_for!(self.state, correlated_loss_channel, pairs, *ps); + return Ok(Effects::None); + } + _ => {} + } + + dispatch_common_paulisum!(self, inst, msg, "LossyPauliSum") + } +} + +impl vihaco::Reset for LossyPauliSumExecutor +where + T: Config, +{ + fn reset(&mut self) { + // TODO(Task 5/6): rebuild self.state from the seeded observable. + } +} + +/// Tableau-backed inner enum (Schrödinger picture). Carries the six +/// size-bucketed `CircuitExecutor` variants; bucket is picked from `n_qubits`. +pub enum TableauCircuit { + Bits64(CircuitExecutor, usize, Vec<(Complex64, usize)>>), + Bits128(CircuitExecutor, u128, Vec<(Complex64, u128)>>), + Bits256(CircuitExecutor, U256, Vec<(Complex64, U256)>>), + Bits512(CircuitExecutor, U512, Vec<(Complex64, U512)>>), + Bits1024(CircuitExecutor, U1024, Vec<(Complex64, U1024)>>), + Bits2048(CircuitExecutor, U2048, Vec<(Complex64, U2048)>>), +} + +impl TableauCircuit { + pub fn new(n_qubits: usize, coefficient_threshold: f64) -> Self { + if n_qubits <= 64 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits64(CircuitExecutor { tab }) + } else if n_qubits <= 128 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits128(CircuitExecutor { tab }) + } else if n_qubits <= 256 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits256(CircuitExecutor { tab }) + } else if n_qubits <= 512 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits512(CircuitExecutor { tab }) + } else if n_qubits <= 1024 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits1024(CircuitExecutor { tab }) + } else if n_qubits <= 2048 { + let tab = GeneralizedTableau::new(n_qubits, coefficient_threshold); + Self::Bits2048(CircuitExecutor { tab }) + } else { + panic!("No matching executor for {} qubits", n_qubits); + } + } + + /// Same as [`TableauCircuit::new`], but seed the RNG deterministically so a + /// shot is reproducible. + pub fn new_with_seed(n_qubits: usize, coefficient_threshold: f64, seed: u64) -> Self { + macro_rules! seeded { + ($variant:ident) => {{ + let tab = GeneralizedTableau::new_with_seed(n_qubits, coefficient_threshold, seed); + Self::$variant(CircuitExecutor { tab }) + }}; + } + if n_qubits <= 64 { + seeded!(Bits64) + } else if n_qubits <= 128 { + seeded!(Bits128) + } else if n_qubits <= 256 { + seeded!(Bits256) + } else if n_qubits <= 512 { + seeded!(Bits512) + } else if n_qubits <= 1024 { + seeded!(Bits1024) + } else if n_qubits <= 2048 { + seeded!(Bits2048) + } else { + panic!("No matching executor for {} qubits", n_qubits); + } + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + match self { + Self::Bits64(ex) => ex.execute_instruction(inst, msg), + Self::Bits128(ex) => ex.execute_instruction(inst, msg), + Self::Bits256(ex) => ex.execute_instruction(inst, msg), + Self::Bits512(ex) => ex.execute_instruction(inst, msg), + Self::Bits1024(ex) => ex.execute_instruction(inst, msg), + Self::Bits2048(ex) => ex.execute_instruction(inst, msg), + } + } + + pub fn state_string(&self) -> String { + match self { + Self::Bits64(ex) => ex.tab.to_string(), + Self::Bits128(ex) => ex.tab.to_string(), + Self::Bits256(ex) => ex.tab.to_string(), + Self::Bits512(ex) => ex.tab.to_string(), + Self::Bits1024(ex) => ex.tab.to_string(), + Self::Bits2048(ex) => ex.tab.to_string(), + } + } +} + +impl vihaco::Reset for TableauCircuit { + fn reset(&mut self) { + match self { + Self::Bits64(ex) => ex.reset(), + Self::Bits128(ex) => ex.reset(), + Self::Bits256(ex) => ex.reset(), + Self::Bits512(ex) => ex.reset(), + Self::Bits1024(ex) => ex.reset(), + Self::Bits2048(ex) => ex.reset(), + }; + } +} + +/// PauliSum-backed inner enum (Heisenberg picture). Per Decision 7 of the plan, +/// the size buckets carry `[u8; N]`-storage `ByteFxHashF64` configs (N = 8, 16, +/// …, 256) rather than the tableau's `[u64; N]` configs; bucket labels match +/// the semantic qubit count (`Bits64` = 64 qubits) so the outer enum's dispatch +/// is uniform across backends. +pub enum PauliSumCircuit { + Bits64(PauliSumExecutor>), + Bits128(PauliSumExecutor>), + Bits256(PauliSumExecutor>), + Bits512(PauliSumExecutor>), + Bits1024(PauliSumExecutor>), + Bits2048(PauliSumExecutor>), +} + +impl PauliSumCircuit { + /// Build a PauliSum-backed circuit, seeding the state with every term: + /// `for (word, coef) in terms { state += (word, coef); }`. Words must + /// already be validated against `info.n_qubits` by the caller. + pub fn new(info: &PPVMDeviceInfo, terms: &[(String, f64)]) -> Self { + macro_rules! build { + ($variant:ident, $N:literal) => {{ + let mut state = PauliSum::>::builder() + .n_qubits(info.n_qubits) + .strategy(paulisum_strategy(info)) + .build(); + for (word, coef) in terms { + state += (word.as_str(), *coef); + } + Self::$variant(PauliSumExecutor { state }) + }}; + } + if info.n_qubits <= 64 { + build!(Bits64, 8) + } else if info.n_qubits <= 128 { + build!(Bits128, 16) + } else if info.n_qubits <= 256 { + build!(Bits256, 32) + } else if info.n_qubits <= 512 { + build!(Bits512, 64) + } else if info.n_qubits <= 1024 { + build!(Bits1024, 128) + } else if info.n_qubits <= 2048 { + build!(Bits2048, 256) + } else { + panic!("No matching PauliSum executor for {} qubits", info.n_qubits); + } + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + match self { + Self::Bits64(ex) => ex.execute_instruction(inst, msg), + Self::Bits128(ex) => ex.execute_instruction(inst, msg), + Self::Bits256(ex) => ex.execute_instruction(inst, msg), + Self::Bits512(ex) => ex.execute_instruction(inst, msg), + Self::Bits1024(ex) => ex.execute_instruction(inst, msg), + Self::Bits2048(ex) => ex.execute_instruction(inst, msg), + } + } + + pub fn state_string(&self) -> String { + match self { + Self::Bits64(ex) => ex.state.to_string(), + Self::Bits128(ex) => ex.state.to_string(), + Self::Bits256(ex) => ex.state.to_string(), + Self::Bits512(ex) => ex.state.to_string(), + Self::Bits1024(ex) => ex.state.to_string(), + Self::Bits2048(ex) => ex.state.to_string(), + } + } +} + +impl vihaco::Reset for PauliSumCircuit { + fn reset(&mut self) { + match self { + Self::Bits64(ex) => ex.reset(), + Self::Bits128(ex) => ex.reset(), + Self::Bits256(ex) => ex.reset(), + Self::Bits512(ex) => ex.reset(), + Self::Bits1024(ex) => ex.reset(), + Self::Bits2048(ex) => ex.reset(), + }; + } +} + +/// LossyPauliSum-backed inner enum. Identical shape to [`PauliSumCircuit`] +/// but with `LossyPauliWord`-keyed configs so loss-channel methods dispatch. +pub enum LossyPauliSumCircuit { + Bits64(LossyPauliSumExecutor>), + Bits128(LossyPauliSumExecutor>), + Bits256(LossyPauliSumExecutor>), + Bits512(LossyPauliSumExecutor>), + Bits1024(LossyPauliSumExecutor>), + Bits2048(LossyPauliSumExecutor>), +} + +impl LossyPauliSumCircuit { + /// Build a LossyPauliSum-backed circuit, seeding every term via + /// `state += (word, coef)`. Words must already be validated against + /// `info.n_qubits` by the caller. + pub fn new(info: &PPVMDeviceInfo, terms: &[(String, f64)]) -> Self { + macro_rules! build { + ($variant:ident, $N:literal) => {{ + let mut state = PauliSum::>::builder() + .n_qubits(info.n_qubits) + .strategy(paulisum_strategy(info)) + .build(); + for (word, coef) in terms { + state += (word.as_str(), *coef); + } + Self::$variant(LossyPauliSumExecutor { state }) + }}; + } + if info.n_qubits <= 64 { + build!(Bits64, 8) + } else if info.n_qubits <= 128 { + build!(Bits128, 16) + } else if info.n_qubits <= 256 { + build!(Bits256, 32) + } else if info.n_qubits <= 512 { + build!(Bits512, 64) + } else if info.n_qubits <= 1024 { + build!(Bits1024, 128) + } else if info.n_qubits <= 2048 { + build!(Bits2048, 256) + } else { + panic!( + "No matching LossyPauliSum executor for {} qubits", + info.n_qubits + ); + } + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + match self { + Self::Bits64(ex) => ex.execute_instruction(inst, msg), + Self::Bits128(ex) => ex.execute_instruction(inst, msg), + Self::Bits256(ex) => ex.execute_instruction(inst, msg), + Self::Bits512(ex) => ex.execute_instruction(inst, msg), + Self::Bits1024(ex) => ex.execute_instruction(inst, msg), + Self::Bits2048(ex) => ex.execute_instruction(inst, msg), + } + } + + pub fn state_string(&self) -> String { + match self { + Self::Bits64(ex) => ex.state.to_string(), + Self::Bits128(ex) => ex.state.to_string(), + Self::Bits256(ex) => ex.state.to_string(), + Self::Bits512(ex) => ex.state.to_string(), + Self::Bits1024(ex) => ex.state.to_string(), + Self::Bits2048(ex) => ex.state.to_string(), + } + } +} + +impl vihaco::Reset for LossyPauliSumCircuit { + fn reset(&mut self) { + match self { + Self::Bits64(ex) => ex.reset(), + Self::Bits128(ex) => ex.reset(), + Self::Bits256(ex) => ex.reset(), + Self::Bits512(ex) => ex.reset(), + Self::Bits1024(ex) => ex.reset(), + Self::Bits2048(ex) => ex.reset(), + }; + } +} + +/// Outer `Circuit` enum: backend selector. Picks one of the three inner enums +/// based on `info.backend` at construction time; from there, every per-step +/// call routes outer → inner → executor. +pub enum Circuit { + Tableau(TableauCircuit), + PauliSum(PauliSumCircuit), + LossyPauliSum(LossyPauliSumCircuit), +} + +#[component(instruction = CircuitInstruction, message = CircuitMessage, effect = CircuitOutcomeEffect)] +impl Circuit { + /// Build a Tableau-backed circuit. Tableau init only needs `n_qubits` and + /// `coefficient_threshold` from `info`; no observable required. + pub fn tableau(info: &PPVMDeviceInfo) -> Self { + Self::Tableau(TableauCircuit::new( + info.n_qubits, + info.coefficient_threshold, + )) + } + + /// Same as [`Circuit::tableau`], but seed the tableau RNG deterministically + /// so a shot is reproducible. + pub fn tableau_with_seed(info: &PPVMDeviceInfo, seed: u64) -> Self { + Self::Tableau(TableauCircuit::new_with_seed( + info.n_qubits, + info.coefficient_threshold, + seed, + )) + } + + /// Build a PauliSum-backed circuit, seeding the state with every term in + /// `terms`. Each `(word, coef)` is added via `state += (word, coef)`; the + /// caller is responsible for having parsed/validated the words against + /// `info.n_qubits` (see `parse_observable_terms` in `composite.rs`). + pub fn paulisum(info: &PPVMDeviceInfo, terms: &[(String, f64)]) -> Self { + Self::PauliSum(PauliSumCircuit::new(info, terms)) + } + + /// Build a LossyPauliSum-backed circuit. Same contract as + /// [`Circuit::paulisum`]. + pub fn lossy_paulisum(info: &PPVMDeviceInfo, terms: &[(String, f64)]) -> Self { + Self::LossyPauliSum(LossyPauliSumCircuit::new(info, terms)) + } + + fn execute( + &mut self, + inst: CircuitInstruction, + msg: CircuitMessage, + ) -> Result> { + self.execute_instruction(&inst, &msg) + } + + fn execute_instruction( + &mut self, + inst: &CircuitInstruction, + msg: &CircuitMessage, + ) -> Result> { + match self { + Self::Tableau(c) => c.execute_instruction(inst, msg), + Self::PauliSum(c) => c.execute_instruction(inst, msg), + Self::LossyPauliSum(c) => c.execute_instruction(inst, msg), + } + } + + /// Render the current state. Used by the REPL's `show` command. + pub fn state_string(&self) -> String { + match self { + Self::Tableau(c) => c.state_string(), + Self::PauliSum(c) => c.state_string(), + Self::LossyPauliSum(c) => c.state_string(), + } + } +} + +#[observe(CircuitEffect, effect=CircuitOutcomeEffect)] +impl Circuit { + fn observe_circuit_effect( + &mut self, + effect: &CircuitEffect, + ) -> Result> { + self.execute_instruction(&effect.inst, &effect.msg) + } +} + +impl vihaco::Reset for Circuit { + fn reset(&mut self) { + match self { + Self::Tableau(c) => c.reset(), + Self::PauliSum(c) => c.reset(), + Self::LossyPauliSum(c) => c.reset(), + }; + } +} + +impl Default for Circuit { + fn default() -> Self { + // Default backend is Tableau, which doesn't require an observable. + Self::tableau(&PPVMDeviceInfo::default()) + } +} diff --git a/crates/ppvm-vihaco/src/composite.rs b/crates/ppvm-vihaco/src/composite.rs new file mode 100644 index 000000000..a09ca6b96 --- /dev/null +++ b/crates/ppvm-vihaco/src/composite.rs @@ -0,0 +1,1296 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use eyre::{Result, eyre}; +use vihaco::frame::Frame; +use vihaco::machine::StackFrame; +use vihaco::observer::stdio::{StdoutEffect, StdoutObserver}; +use vihaco::traits::{GetProgramGlobal, ProgramCounter, StackMemory}; +use vihaco::{Effects, Observe, ProgramLoader, Value, composite, observe}; +use vihaco_cpu::{CPU, CPUMessage}; + +/// Re-exported so consumers (e.g. the CLI debugger) can match on step results +/// without depending on `vihaco-cpu` directly. +pub use vihaco_cpu::StepOutcome; + +use crate::component::Circuit; +#[cfg(test)] +use crate::component::TableauCircuit; +use crate::measurements::{ + CircuitOutcomeEffect, MeasurementEffect, MeasurementObserver, MeasurementResult, TraceEffect, + TraceObserver, +}; +use vihaco_circuit_isa::{CircuitEffect, CircuitInstruction, CircuitMessage}; + +pub const PPVM_MAGIC: u32 = 0x5050564D; + +/// Which execution backend the circuit runs on. Selected via the +/// `device circuit.backend` header; defaults to `Tableau` so existing +/// programs that don't declare a backend keep working. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, vihaco_parser::Parse)] +pub enum BackendKind { + #[default] + Tableau, + PauliSum, + #[token = "lossy_paulisum"] + LossyPauliSum, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PPVMDeviceInfo { + pub magic: u32, + pub n_qubits: usize, + pub coefficient_threshold: f64, + pub backend: BackendKind, + pub observable: Option, + pub max_pauli_weight: Option, +} + +impl Default for PPVMDeviceInfo { + fn default() -> Self { + Self { + magic: PPVM_MAGIC, + n_qubits: 0, + coefficient_threshold: 1e-10, + backend: BackendKind::default(), + observable: None, + max_pauli_weight: None, + } + } +} + +pub type Instruction = PPVMInstruction; + +#[composite] +#[derive(Default)] +pub struct PPVM { + #[program] + loader: ProgramLoader, + + #[device(0x00)] + cpu: CPU, + + #[device(0x01)] + circuit: Circuit, + + stdout: StdoutObserver, + + measurement_record: MeasurementObserver, + + trace_record: TraceObserver, +} + +#[derive(Debug, Clone)] +pub enum PPVMEffect { + Step(StepOutcome), + Stdout(StdoutEffect), + Circuit(Box), + Measurement(MeasurementEffect), + Trace(TraceEffect), +} + +#[observe(vihaco::observer::stdio::StdoutEffect, effect = PPVMEffect)] +impl PPVM { + fn observe_stdout_effect(&mut self, effect: &StdoutEffect) -> eyre::Result> { + Observe::::observe(&mut self.stdout, effect) + } +} + +#[observe(CircuitEffect, effect = PPVMEffect)] +impl PPVM { + fn observe_circuit_effect( + &mut self, + effect: &CircuitEffect, + ) -> eyre::Result> { + Observe::::observe(&mut self.circuit, effect) + } +} + +#[observe(MeasurementEffect, effect = PPVMEffect)] +impl PPVM { + fn observe_measurement_effect( + &mut self, + effect: &MeasurementEffect, + ) -> eyre::Result> { + Observe::::observe(&mut self.measurement_record, effect) + } +} + +#[observe(TraceEffect, effect = PPVMEffect)] +impl PPVM { + fn observe_trace_effect(&mut self, effect: &TraceEffect) -> eyre::Result> { + Observe::::observe(&mut self.trace_record, effect) + } +} + +impl From for PPVMEffect { + fn from(value: StdoutEffect) -> Self { + Self::Stdout(value) + } +} + +impl From for PPVMEffect { + fn from(value: MeasurementEffect) -> Self { + Self::Measurement(value) + } +} + +impl From for PPVMEffect { + fn from(value: TraceEffect) -> Self { + Self::Trace(value) + } +} + +impl From for PPVMEffect { + fn from(value: CircuitOutcomeEffect) -> Self { + match value { + CircuitOutcomeEffect::Measurement(m) => Self::Measurement(m), + CircuitOutcomeEffect::Trace(t) => Self::Trace(t), + } + } +} + +impl std::fmt::Display for PPVMInstruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PPVMInstruction::Cpu(inst) => inst.fmt(f), + PPVMInstruction::Circuit(inst) => inst.fmt(f), + } + } +} + +impl PartialEq for PPVMInstruction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (PPVMInstruction::Cpu(a), PPVMInstruction::Cpu(b)) => a == b, + (PPVMInstruction::Circuit(a), PPVMInstruction::Circuit(b)) => a == b, + _ => false, + } + } +} + +impl From for PPVMInstruction { + fn from(value: vihaco_cpu::Instruction) -> Self { + Self::Cpu(value) + } +} + +impl From for PPVMInstruction { + fn from(value: CircuitInstruction) -> Self { + Self::Circuit(value) + } +} + +impl PPVM { + fn resolve_cpu(&mut self, inst: &vihaco_cpu::Instruction) -> eyre::Result { + match inst { + vihaco_cpu::Instruction::IndirectCall => { + let function_id: u32 = self.cpu.stack_top()?.get_function_ref()?; + let function = self.loader.get_function(function_id as usize)?; + Ok(CPUMessage::FunctionInfo { + arity: function.signature.params.len() as u32, + start_address: function.start_address, + }) + } + vihaco_cpu::Instruction::Print => { + let value = *self.cpu.stack_top()?; + match value { + vihaco::Value::String(addr) => { + let string = self.loader.get_string(addr as usize)?.clone(); + Ok(CPUMessage::Print(string)) + } + value => Ok(CPUMessage::Print(value.to_string())), + } + } + _ => Ok(CPUMessage::None), + } + } + + fn resolve_circuit(&mut self, inst: &CircuitInstruction) -> eyre::Result { + use CircuitInstruction::*; + match inst { + X | Y | Z | H | S | SAdj | SqrtX | SqrtY | SqrtXAdj | SqrtYAdj | T | TAdj | Measure + | Reset => { + let q = self.pop_qubit()?; + Ok(CircuitMessage::Qubit(q)) + } + CNOT | CZ => { + let q1 = self.pop_qubit()?; + let q0 = self.pop_qubit()?; + Ok(CircuitMessage::TwoQubit(q0, q1)) + } + RX | RY | RZ | Depolarize | Loss => { + let theta = self.pop_f64()?; + let q = self.pop_qubit()?; + Ok(CircuitMessage::QubitAndFloat(q, theta)) + } + RXX | RYY | RZZ | Depolarize2 => { + let theta = self.pop_f64()?; + let q1 = self.pop_qubit()?; + let q0 = self.pop_qubit()?; + Ok(CircuitMessage::TwoQubitAndFloat(q0, q1, theta)) + } + R => { + let theta = self.pop_f64()?; + let axis_angle = self.pop_f64()?; + let q = self.pop_qubit()?; + Ok(CircuitMessage::QubitAndTwoFloats(q, axis_angle, theta)) + } + U3 => { + let lam = self.pop_f64()?; + let phi = self.pop_f64()?; + let theta = self.pop_f64()?; + let q = self.pop_qubit()?; + Ok(CircuitMessage::QubitU3(q, theta, phi, lam)) + } + + // TODO: pop actual float arrays? + PauliError => { + let pz = self.pop_f64()?; + let py = self.pop_f64()?; + let px = self.pop_f64()?; + let q = self.pop_qubit()?; + Ok(CircuitMessage::QubitAndFloatArr3(q, [px, py, pz])) + } + CorrelatedLoss => { + let p2 = self.pop_f64()?; + let p1 = self.pop_f64()?; + let p0 = self.pop_f64()?; + let q1 = self.pop_qubit()?; + let q0 = self.pop_qubit()?; + Ok(CircuitMessage::TwoQubitAndFloatArr3(q0, q1, [p0, p1, p2])) + } + TwoQubitPauliError => { + let mut ps = [0.0; 15]; + for p in ps.iter_mut().rev() { + *p = self.pop_f64()?; + } + let q1 = self.pop_qubit()?; + let q0 = self.pop_qubit()?; + Ok(CircuitMessage::TwoQubitAndFloatArr15(q0, q1, ps)) + } + Trace => { + let s = self.pop_string()?; + Ok(CircuitMessage::PauliPatternStr(s)) + } + Truncate => Ok(CircuitMessage::None), + } + } + + fn pop_qubit(&mut self) -> eyre::Result { + match self.cpu.stack_pop()? { + vihaco::Value::U32(v) => Ok(v as usize), + vihaco::Value::U64(v) => usize::try_from(v).map_err(Into::into), + vihaco::Value::I64(v) => usize::try_from(v).map_err(Into::into), + v => Err(eyre::eyre!("Expected qubit address, got {:?}", v)), + } + } + + fn pop_f64(&mut self) -> eyre::Result { + match self.cpu.stack_pop()? { + vihaco::Value::F64(v) => Ok(v), + v => Err(eyre::eyre!("Expected f64 argument, got {:?}", v)), + } + } + + /// Pop a `Value::String(addr)`, look up the addr in the module's string + /// table, and return the owned string. Used by `Trace` to resolve its + /// Pauli-pattern operand before the executor sees the message. + fn pop_string(&mut self) -> eyre::Result { + match self.cpu.stack_pop()? { + vihaco::Value::String(addr) => Ok(self.loader.get_string(addr as usize)?.clone()), + v => Err(eyre::eyre!("Expected string operand, got {:?}", v)), + } + } + + pub fn init(&mut self) -> eyre::Result<()> { + self.init_inner(None) + } + + /// Like [`PPVM::init`], but seed the circuit RNG deterministically so the + /// run is reproducible. + pub fn init_with_seed(&mut self, seed: u64) -> eyre::Result<()> { + self.init_inner(Some(seed)) + } + + fn init_inner(&mut self, seed: Option) -> eyre::Result<()> { + let info = &self.loader.module.extra; + if info.n_qubits == 0 { + return Err(eyre::eyre!("device circuit.n_qubits must be declared")); + } + self.circuit = match (info.backend, seed) { + (BackendKind::Tableau, None) => Circuit::tableau(info), + (BackendKind::Tableau, Some(seed)) => Circuit::tableau_with_seed(info, seed), + (BackendKind::PauliSum, _) => { + let terms = parse_observable_terms(info)?; + Circuit::paulisum(info, &terms) + } + (BackendKind::LossyPauliSum, _) => { + let terms = parse_observable_terms(info)?; + Circuit::lossy_paulisum(info, &terms) + } + }; + + // push entry frame + self.cpu.push_frame(Frame { + base: 0, + span: (0, 0, 0), + function: None, + ret_pc: 0, + }); + + Ok(()) + } + + pub fn load( + &mut self, + module: &vihaco::module::Module, + ) -> eyre::Result<()> { + self.loader.module = module.clone(); + Ok(()) + } + + pub fn step_once(&mut self) -> eyre::Result { + let inst = self.peek_instruction()?.clone(); + let effects = self.execute_effects(inst)?; + self.continue_effects(effects) + } + + /// Program counter: the index of the next instruction to execute. + pub fn current_pc(&self) -> u32 { + self.loader.pc() + } + + /// The next instruction to execute, or `None` once execution has run off + /// the end of the code. Intended for debuggers/inspection. + pub fn current_instruction(&self) -> Option { + self.peek_instruction().ok().cloned() + } + + /// Append one REPL command's lowered VM ops and run just that block against + /// the persistent state, advancing the pc through it. NOTE: an out-of-range + /// qubit index *panics* in the tableau rather than erroring, so callers must + /// bounds-check qubit operands against `n_qubits` first. + pub fn execute_single_instruction(&mut self, instrs: &[PPVMInstruction]) -> eyre::Result<()> { + let start = self.loader.module.code.len() as u32; + self.loader.module.code.extend_from_slice(instrs); + *self.loader.pc_mut() = start; + for _ in 0..instrs.len() { + self.step_once()?; + } + Ok(()) + } + + /// Render the current circuit state (tableau / Pauli sum) for the REPL's + /// `show` command. Delegates to the circuit's size-specific tableau. + pub fn state_string(&self) -> String { + self.circuit.state_string() + } + + /// Build a fresh, initialized `n_qubits`-qubit device with no code. The + /// REPL's `device` command uses this to (re)create the machine. Errors if + /// `n_qubits` is zero (a device must have at least one qubit). + pub fn with_qubits(n_qubits: usize) -> eyre::Result { + let mut machine = Self::default(); + let mut module = vihaco::module::Module::< + PPVMInstruction, + Value, + vihaco::Type, + PPVMDeviceInfo, + >::default(); + module.extra.n_qubits = n_qubits; + machine.load(&module)?; + machine.init()?; + Ok(machine) + } + + /// Lower a single circuit instruction — qubit operands first, then float + /// params, per the push-in-order / pop-in-reverse convention — and execute + /// it against the persistent state. Qubit indices are bounds-checked against + /// the device size first, because the tableau panics (rather than erroring) + /// on an out-of-range qubit. + pub fn apply_circuit_instruction( + &mut self, + inst: CircuitInstruction, + qubits: &[usize], + params: &[f64], + ) -> eyre::Result<()> { + let n_qubits = self.loader.module.extra.n_qubits; + for &q in qubits { + if q >= n_qubits { + eyre::bail!("qubit {q} out of range for {n_qubits}-qubit device"); + } + } + + let mut instrs = Vec::with_capacity(qubits.len() + params.len() + 1); + for &q in qubits { + instrs.push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::U64(q as u64), + ))); + } + for &p in params { + instrs.push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::F64(p), + ))); + } + instrs.push(PPVMInstruction::Circuit(inst)); + self.execute_single_instruction(&instrs) + } + + fn execute_effects(&mut self, inst: Instruction) -> eyre::Result> { + log::debug!("exec inst: {:?}, stack: {:?}", inst, self.cpu.stack()); + match inst { + PPVMInstruction::Cpu(cpu_inst) => { + let msg = self.resolve_cpu(&cpu_inst)?; + self.cpu.set_current_pc(self.loader.pc()); + let stdout_effect = match (&cpu_inst, &msg) { + (vihaco_cpu::Instruction::Print, vihaco_cpu::CPUMessage::Print(text)) => { + Some(PPVMEffect::Stdout(StdoutEffect(text.clone()))) + } + _ => None, + }; + let outcome = vihaco::expect_exactly_one_effect( + vihaco::GeneratedComponent::execute_generated(&mut self.cpu, cpu_inst, msg)?, + )?; + // Advance past a breakpoint as well, so the debugger that paused + // on it doesn't re-hit the same instruction on the next step. + if matches!(outcome, StepOutcome::Continue | StepOutcome::Breakpoint) { + if let Some(target) = self.cpu.take_pending_pc() { + *self.loader.pc_mut() = target; + } else { + *self.loader.pc_mut() += 1; + } + } + let mut effects = Effects::one(PPVMEffect::Step(outcome)); + if let Some(stdout_effect) = stdout_effect { + effects = effects.append(stdout_effect); + } + Ok(effects) + } + PPVMInstruction::Circuit(inst) => { + let msg = self.resolve_circuit(&inst)?; + let circuit_effects = ::execute_generated( + &mut self.circuit, + inst, + msg, + )?; + *self.loader.pc_mut() += 1; + let mut effects = Effects::one(PPVMEffect::Step(StepOutcome::Continue)); + for outcome in circuit_effects { + effects = effects.append(PPVMEffect::from(outcome)); + } + Ok(effects) + } + } + } + + fn continue_effects(&mut self, effects: Effects) -> eyre::Result { + let mut step_outcome = None; + for effect in effects { + match effect { + PPVMEffect::Step(outcome) => { + if step_outcome.replace(outcome).is_some() { + return Err(eyre::eyre!( + "expected exactly one PPVM step effect, got multiple" + )); + } + } + effect => self.continue_observer_effect(effect)?, + } + } + + step_outcome.ok_or_else(|| eyre::eyre!("expected exactly one PPVM step effect, got 0")) + } + + fn continue_observer_effect(&mut self, effect: PPVMEffect) -> eyre::Result<()> { + match effect { + PPVMEffect::Stdout(effect) => { + let follow_ups = Observe::::observe(self, &effect)?; + self.continue_observer_effects(follow_ups) + } + PPVMEffect::Circuit(effect) => { + let follow_ups = Observe::::observe(self, &effect)?; + self.continue_observer_effects(follow_ups) + } + PPVMEffect::Measurement(effect) => { + let follow_ups = Observe::::observe(self, &effect)?; + // NOTE: push measurements to stack; two booleans: outcome, is_lost + for outcome in effect.measurement_results { + let m = Value::U32(outcome as u32); + self.cpu.stack_push(m); + } + self.continue_observer_effects(follow_ups) + } + PPVMEffect::Trace(effect) => { + let value = effect.value; + let follow_ups = Observe::::observe(self, &effect)?; + // Mirror the measurement wiring: append to the trace record + // (via the observer above) AND push the value onto the CPU + // stack so user bytecode can consume it. Plan Task 7. + self.cpu.stack_push(Value::F64(value)); + self.continue_observer_effects(follow_ups) + } + PPVMEffect::Step(_) => Err(eyre::eyre!( + "unexpected Step effect while continuing PPVM observer follow-ups" + )), + } + } + + fn continue_observer_effects(&mut self, effects: Effects) -> eyre::Result<()> { + for effect in effects { + self.continue_observer_effect(effect)?; + } + Ok(()) + } + + pub fn run(&mut self) -> eyre::Result { + self.run_with_seed(None) + } + + /// Like [`PPVM::run`], but seed the circuit RNG deterministically when + /// `seed` is `Some`, making the run reproducible. + pub fn run_with_seed(&mut self, seed: Option) -> eyre::Result { + match seed { + Some(seed) => self.init_with_seed(seed)?, + None => self.init()?, + } + + loop { + // Breakpoints only pause the interactive debugger; a batch run + // skips straight past them. + match self.step_once()? { + StepOutcome::Continue | StepOutcome::Breakpoint => continue, + action => return Ok(action), + } + } + } + + pub fn stdout(&self) -> &[u8] { + self.stdout.output() + } + + pub fn measurement_record(&self) -> Vec { + self.measurement_record.record.clone() + } + + /// Per-trace values collected by `Trace` instructions during the run. + /// Parallel to [`PPVM::measurement_record`]: one f64 per `Trace` executed, + /// in execution order. + pub fn trace_record(&self) -> Vec { + self.trace_record.record.clone() + } + + pub fn load_program(&mut self, program: &str) -> eyre::Result<()> { + let module = crate::compile_program(program)?; + self.load(&module)?; + Ok(()) + } + + /// Load from a file, auto-detecting the format: if it starts with the PPVM + /// magic it is loaded as `.ssb` bytecode, otherwise it is parsed as `.sst` + /// source text. A magic match commits to the bytecode path — a corrupt + /// `.ssb` errors rather than silently falling back to the text parser. + pub fn load_file(&mut self, path: &str) -> eyre::Result<()> { + let bytes = std::fs::read(path)?; + if crate::bytecode::is_bytecode(&bytes) { + self.load_bytecode(&bytes) + } else { + self.load_program(std::str::from_utf8(&bytes)?) + } + } + + /// Load a module from an in-memory `.ssb` byte stream. + pub fn load_bytecode(&mut self, bytes: &[u8]) -> eyre::Result<()> { + let module = crate::bytecode::module_from_bytes(bytes)?; + self.load(&module) + } + + /// Read a `.ssb` file and load the module it contains. + pub fn load_bytecode_file(&mut self, path: &str) -> eyre::Result<()> { + let bytes = std::fs::read(path)?; + self.load_bytecode(&bytes) + } + + pub fn run_program(&mut self, program: &str) -> eyre::Result<()> { + self.load_program(program)?; + self.run()?; + Ok(()) + } + + pub fn run_file(&mut self, path: &str) -> eyre::Result<()> { + self.load_file(path)?; + self.run()?; + Ok(()) + } +} + +impl vihaco::Reset for PPVM { + fn reset(&mut self) { + self.cpu.reset(); + self.circuit.reset(); + self.loader.pc = 0; + self.measurement_record.record.clear(); + self.trace_record.record.clear(); + } +} + +/// Parse the `device circuit.observable` header into Pauli-sum terms ready to +/// seed a `PauliSum` / `LossyPauliSum` state. Single-Pauli observables from +/// Phase 2 keep working as the degenerate one-term case; multi-term sums like +/// `"1.0*ZZ + 0.5*XX"` are handled by [`parse_pauli_sum_terms`]. +fn parse_observable_terms(info: &PPVMDeviceInfo) -> Result> { + let observable = info.observable.as_deref().ok_or_else(|| { + eyre!( + "the {:?} backend requires `device circuit.observable` to be set", + info.backend + ) + })?; + crate::observable::parse_pauli_sum_terms(observable, info.n_qubits) +} + +#[cfg(test)] +mod tests { + use vihaco::{Type, Value, module::Module}; + + use super::*; + + #[test] + fn test_run_ppvm() -> eyre::Result<()> { + let mut module: Module = Module::default(); + + module.extra.n_qubits = 2; + + /* + const.u64 0 + circuit h + */ + let zero = PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const(Value::U64(0))); + let one = PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const(Value::U64(1))); + module.code.push(zero.clone()); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::H)); + + /* + const.u64 0 + circuit t + */ + + module.code.push(zero.clone()); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::T)); + + /* + const.u64 0 + const.u64 1 + circuit cnot + */ + module.code.push(zero.clone()); + module.code.push(one.clone()); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::CNOT)); + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + + for _ in 0..module.code.len() { + machine.step_once()?; + assert!(machine.cpu.stack().len() <= 2); + } + + let num_coefficients = match &machine.circuit { + Circuit::Tableau(TableauCircuit::Bits64(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::Tableau(TableauCircuit::Bits128(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::Tableau(TableauCircuit::Bits256(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::Tableau(TableauCircuit::Bits512(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::Tableau(TableauCircuit::Bits1024(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::Tableau(TableauCircuit::Bits2048(ex)) => { + println!("{}", ex.tab); + ex.tab.coefficients.len() + } + Circuit::PauliSum(_) | Circuit::LossyPauliSum(_) => { + panic!("test expects the default Tableau backend, got a PauliSum variant"); + } + }; + + assert_eq!(num_coefficients, 2); + Ok(()) + } + + #[test] + fn test_device_decl() -> eyre::Result<()> { + // Equivalent .sst source: + // + // device circuit.n_qubits 5; + // device circuit.coefficient_threshold 1e-10; + // + // fn @main() { ...5-qubit GHZ + 5 measurements... } + + let mut module: Module = Module::default(); + + module.extra.n_qubits = 5; + module.extra.coefficient_threshold = 1e-10; + + // 5-qubit GHZ: H on q0, then CNOT(q_i, q_{i+1}) for i = 0..4. + /* + const.u64 0 + circuit h + */ + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::U64(0), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::H)); + + for i in 0..4u64 { + /* + const.u64 i + const.u64 i+1 + circuit cnot + */ + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::U64(i), + ))); + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::U64(i + 1), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::CNOT)); + } + + // Measure all 5 qubits. + for q in 0..5u64 { + /* + const.u64 q + circuit measure + */ + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::U64(q), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Measure)); + } + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + + for _ in 0..module.code.len() { + machine.step_once()?; + } + + assert_eq!(machine.measurement_record().len(), 5); + Ok(()) + } + + // ─── Incremental execution (REPL) ───────────────────────────────────── + + #[test] + fn execute_single_instruction_persists_state_across_calls() -> eyre::Result<()> { + use crate::measurements::MeasurementOutcome; + + // A 1-qubit device with no code; the REPL builds up instructions + // incrementally, one command at a time, rather than loading a program. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + + // First command: X on q0 (|0> -> |1>). + let x = [ + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const(Value::U64(0))), + PPVMInstruction::Circuit(CircuitInstruction::X), + ]; + machine.execute_single_instruction(&x)?; + // No measurement yet. + assert!(machine.measurement_record().is_empty()); + + // Second command: measure q0. The X from the first command must persist, + // so the outcome is deterministically |1>. + let measure = [ + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const(Value::U64(0))), + PPVMInstruction::Circuit(CircuitInstruction::Measure), + ]; + machine.execute_single_instruction(&measure)?; + + let record = machine.measurement_record(); + assert_eq!(record.len(), 1); + assert_eq!(record[0].as_slice(), [MeasurementOutcome::One]); + Ok(()) + } + + #[test] + fn execute_single_instruction_propagates_engine_errors() -> eyre::Result<()> { + // The REPL relies on engine errors surfacing as `Err` (so it can print + // them and keep looping) rather than panicking. A circuit with no qubit + // operand on the stack is one such propagating error. + // + // NOTE: an out-of-range qubit index (>= n_qubits) currently *panics* in + // the tableau rather than erroring, so the REPL command layer must + // bounds-check qubit indices before calling `execute`. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + + // `circuit h` with nothing on the stack: `pop_qubit` fails. + let missing_operand = [PPVMInstruction::Circuit(CircuitInstruction::H)]; + assert!( + machine + .execute_single_instruction(&missing_operand) + .is_err() + ); + Ok(()) + } + + #[test] + fn state_string_renders_a_small_device() -> eyre::Result<()> { + let source = "device circuit.n_qubits 2;\nfn @main() { ret }\n"; + let mut machine = PPVM::default(); + machine.load_program(source)?; + machine.init()?; + + let rendered = machine.state_string(); + assert!( + !rendered.is_empty(), + "state_string should render the tableau" + ); + // PPVM delegates to the circuit. + assert_eq!(rendered, machine.circuit.state_string()); + Ok(()) + } + + #[test] + fn resolve_circuit_pops_operands_in_reverse_of_push_order() -> eyre::Result<()> { + // Convention: operands are pushed in argument order (q0, q1, then any + // floats) and popped in reverse. So every two-qubit circuit must read q0 as + // the first operand pushed, consistently, with or without trailing + // floats. (CNOT already obeyed this; the float-carrying arms did not.) + let mut module: Module = Module::default(); + module.extra.n_qubits = 8; + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + + // CNOT: push q0=2, q1=5. + machine.cpu.stack_push(Value::U32(2)); + machine.cpu.stack_push(Value::U32(5)); + assert_eq!( + machine.resolve_circuit(&CircuitInstruction::CNOT)?, + CircuitMessage::TwoQubit(2, 5) + ); + + // RXX: push q0=2, q1=5, theta — same qubit order as CNOT. + machine.cpu.stack_push(Value::U32(2)); + machine.cpu.stack_push(Value::U32(5)); + machine.cpu.stack_push(Value::F64(0.3)); + assert_eq!( + machine.resolve_circuit(&CircuitInstruction::RXX)?, + CircuitMessage::TwoQubitAndFloat(2, 5, 0.3) + ); + + // CorrelatedLoss: push q0=2, q1=5, p0, p1, p2. + machine.cpu.stack_push(Value::U32(2)); + machine.cpu.stack_push(Value::U32(5)); + machine.cpu.stack_push(Value::F64(0.1)); + machine.cpu.stack_push(Value::F64(0.2)); + machine.cpu.stack_push(Value::F64(0.3)); + assert_eq!( + machine.resolve_circuit(&CircuitInstruction::CorrelatedLoss)?, + CircuitMessage::TwoQubitAndFloatArr3(2, 5, [0.1, 0.2, 0.3]) + ); + Ok(()) + } + + #[test] + fn with_qubits_builds_an_initialized_device() -> eyre::Result<()> { + use crate::measurements::MeasurementOutcome; + + let mut m = PPVM::with_qubits(2)?; + // The device is ready to take instructions immediately. + m.apply_circuit_instruction(CircuitInstruction::X, &[0], &[])?; + m.apply_circuit_instruction(CircuitInstruction::Measure, &[0], &[])?; + let record = m.measurement_record(); + assert_eq!(record.len(), 1); + assert_eq!(record[0].as_slice(), [MeasurementOutcome::One]); + Ok(()) + } + + #[test] + fn with_qubits_zero_is_an_error() { + assert!(PPVM::with_qubits(0).is_err()); + } + + #[test] + fn apply_circuit_instruction_bounds_checks_qubits() -> eyre::Result<()> { + // q1 is out of range on a 1-qubit device: this must error rather than + // panic in the tableau. + let mut m = PPVM::with_qubits(1)?; + let err = m + .apply_circuit_instruction(CircuitInstruction::X, &[1], &[]) + .unwrap_err(); + assert!(err.to_string().contains("out of range"), "got: {err}"); + Ok(()) + } + + // ─── Parser-driven entry points ─────────────────────────────────────── + + #[test] + fn load_program_populates_device_info_and_code() -> eyre::Result<()> { + let source = "device circuit.n_qubits 2;\n\ + device circuit.coefficient_threshold 1e-8;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + ret\n\ + }\n"; + let mut machine = PPVM::default(); + machine.load_program(source)?; + assert_eq!(machine.loader.module.extra.n_qubits, 2); + assert_eq!(machine.loader.module.extra.coefficient_threshold, 1e-8); + // const.u64 0 / circuit h / ret = 3 + assert_eq!(machine.loader.module.code.len(), 3); + Ok(()) + } + + #[test] + fn run_program_executes_bell_circuit() -> eyre::Result<()> { + let source = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + const.u64 0\n\ + const.u64 1\n\ + circuit cnot\n\ + const.u64 0\n\ + circuit measure\n\ + const.u64 1\n\ + circuit measure\n\ + ret\n\ + }\n"; + let mut machine = PPVM::default(); + machine.run_program(source)?; + let record = machine.measurement_record(); + assert_eq!(record.len(), 2); + Ok(()) + } + + #[test] + fn reset_clears_the_measurement_record() -> eyre::Result<()> { + use vihaco::Reset; + + let source = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + const.u64 0\n\ + const.u64 1\n\ + circuit cnot\n\ + const.u64 0\n\ + circuit measure\n\ + const.u64 1\n\ + circuit measure\n\ + ret\n\ + }\n"; + let mut machine = PPVM::default(); + machine.run_program(source)?; + assert_eq!(machine.measurement_record().len(), 2); + + // Resetting the machine must discard the recorded measurements, so a + // subsequent run does not see stale results leaking in from before. + machine.reset(); + assert!( + machine.measurement_record().is_empty(), + "reset must clear the measurement record" + ); + + Ok(()) + } + + #[test] + fn init_fails_when_n_qubits_undeclared() -> eyre::Result<()> { + let source = "fn @main() { ret }\n"; + let mut machine = PPVM::default(); + machine.load_program(source)?; + let err = machine.init().unwrap_err(); + assert!(err.to_string().contains("circuit.n_qubits"), "err: {err}"); + Ok(()) + } + + #[test] + fn run_program_reports_parse_errors() { + let source = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + circuit not_a_real_gate\n\ + ret\n\ + }\n"; + let mut machine = PPVM::default(); + let err = machine.run_program(source).unwrap_err(); + assert!( + err.to_string().contains("parsing failed") + || err.to_string().contains("unhandled raw form"), + "err: {err}" + ); + } + + // ─── Breakpoints ────────────────────────────────────────────────────── + + /// Bell circuit with a `breakpoint` between the two measurements. + const BREAKPOINT_PROGRAM: &str = "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + const.u64 0\n\ + const.u64 1\n\ + circuit cnot\n\ + const.u64 0\n\ + circuit measure\n\ + breakpoint\n\ + const.u64 1\n\ + circuit measure\n\ + ret\n\ + }\n"; + + #[test] + fn run_ignores_breakpoints() -> eyre::Result<()> { + // A batch run must execute straight through the breakpoint and record + // both measurements, exactly as if it weren't there. + let mut machine = PPVM::default(); + machine.run_program(BREAKPOINT_PROGRAM)?; + assert_eq!(machine.measurement_record().len(), 2); + Ok(()) + } + + #[test] + fn step_once_advances_past_breakpoint() -> eyre::Result<()> { + let mut machine = PPVM::default(); + machine.load_program(BREAKPOINT_PROGRAM)?; + machine.init()?; + + // Step until the breakpoint pauses us. + let mut outcome = StepOutcome::Continue; + for _ in 0..machine.loader.module.code.len() { + outcome = machine.step_once()?; + if outcome == StepOutcome::Breakpoint { + break; + } + } + assert_eq!(outcome, StepOutcome::Breakpoint, "breakpoint should pause"); + let pc_at_break = machine.current_pc(); + + // Stepping again must make progress (advance the pc) rather than + // re-hitting the same breakpoint instruction. + let next = machine.step_once()?; + assert_ne!( + next, + StepOutcome::Breakpoint, + "must move past the breakpoint" + ); + assert!(machine.current_pc() > pc_at_break, "pc must advance"); + + Ok(()) + } + + #[test] + fn paulisum_truncate_runs_without_error() -> eyre::Result<()> { + // Smoke test: a `circuit truncate` reaches the PauliSum executor's + // Truncate arm and calls `state.truncate()`. Task 8 makes the + // observable mandatory for PauliSum init, so seed `Z` here. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + module.extra.backend = BackendKind::PauliSum; + module.extra.observable = Some("Z".to_string()); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Truncate)); + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + machine.step_once()?; + Ok(()) + } + + #[test] + fn paulisum_trace_populates_trace_record() -> eyre::Result<()> { + // End-to-end Trace pipeline: with the observable `Z` seeded (Task 8), + // tracing the `Z0` pattern picks up that one term with coefficient + // 1.0, so the trace should be exactly 1.0. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + module.extra.backend = BackendKind::PauliSum; + module.extra.observable = Some("Z".to_string()); + // `Z0` matches a Z on qubit 0; the parser requires position anchors. + module.strings.push("Z0".to_string()); + + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::String(0), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Trace)); + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + for _ in 0..module.code.len() { + machine.step_once()?; + } + + assert_eq!(machine.trace_record(), vec![1.0]); + Ok(()) + } + + #[test] + fn paulisum_multi_term_observable_seeds_all_terms() -> eyre::Result<()> { + // Task 11: a sum-valued observable seeds every term. With + // `"ZZ + 0.5*XX"` the state holds `1.0 * ZZ + 0.5 * XX`; tracing + // `[XZ]0[XZ]1` matches both words and returns 1.0 + 0.5 = 1.5. + let mut module: Module = Module::default(); + module.extra.n_qubits = 2; + module.extra.backend = BackendKind::PauliSum; + module.extra.observable = Some("ZZ + 0.5*XX".to_string()); + module.strings.push("[XZ]0[XZ]1".to_string()); + + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::String(0), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Trace)); + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + for _ in 0..module.code.len() { + machine.step_once()?; + } + + assert_eq!(machine.trace_record(), vec![1.5]); + Ok(()) + } + + #[test] + fn paulisum_init_rejects_missing_observable() { + // Task 8 requires `device circuit.observable` for PauliSum / Lossy. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + module.extra.backend = BackendKind::PauliSum; + + let mut machine = PPVM::default(); + machine.load(&module).unwrap(); + let err = machine.init().unwrap_err(); + assert!( + err.to_string().contains("observable"), + "expected observable-related error, got: {err}" + ); + } + + #[test] + fn paulisum_init_rejects_mismatched_observable_length() { + let mut module: Module = Module::default(); + module.extra.n_qubits = 2; + module.extra.backend = BackendKind::PauliSum; + // Three letters but only two qubits — should error. + module.extra.observable = Some("ZZZ".to_string()); + + let mut machine = PPVM::default(); + machine.load(&module).unwrap(); + let err = machine.init().unwrap_err(); + assert!( + err.to_string().contains("invalid Pauli-sum"), + "expected parser rejection, got: {err}" + ); + } + + #[test] + fn tableau_truncate_is_silent_no_op() -> eyre::Result<()> { + // Task 9: `circuit truncate` on the default Tableau backend should run + // without error — the tableau prunes via coefficient_threshold during + // every gate, so the explicit Truncate instruction has nothing to do. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + // backend defaults to Tableau; no observable needed. + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Truncate)); + + let mut machine = PPVM::default(); + machine.load(&module)?; + machine.init()?; + machine.step_once()?; + // No observer effects emitted by Truncate. + assert!(machine.measurement_record().is_empty()); + assert!(machine.trace_record().is_empty()); + Ok(()) + } + + #[test] + fn tableau_trace_emits_expectation_on_zero_state() { + // Task 16: `circuit trace` on the Tableau backend now computes + // Σ_{P matches pat} ⟨ψ|P|ψ⟩ via `GeneralizedTableau::trace`. On the + // freshly-initialized |0⟩ state, pattern `Z0` matches the single + // Pauli Z and ⟨0|Z|0⟩ = 1, so the trace_record gets one entry: 1.0. + let mut module: Module = Module::default(); + module.extra.n_qubits = 1; + module.strings.push("Z0".to_string()); + module + .code + .push(PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const( + Value::String(0), + ))); + module + .code + .push(PPVMInstruction::Circuit(CircuitInstruction::Trace)); + + let mut machine = PPVM::default(); + machine.load(&module).unwrap(); + machine.init().unwrap(); + machine.step_once().unwrap(); // const.string + machine.step_once().unwrap(); // circuit trace + let trace = machine.trace_record(); + assert_eq!(trace.len(), 1); + assert!( + (trace[0] - 1.0).abs() < 1e-12, + "expected ⟨0|Z|0⟩ = 1.0, got {}", + trace[0] + ); + } +} diff --git a/crates/ppvm-vihaco/src/lib.rs b/crates/ppvm-vihaco/src/lib.rs new file mode 100644 index 000000000..ecd8a8cba --- /dev/null +++ b/crates/ppvm-vihaco/src/lib.rs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +pub mod bytecode; +pub mod component; +pub mod composite; +pub mod measurements; +pub mod observable; +pub mod shots; +mod syntax; + +/// Re-exported so consumers (e.g. the CLI REPL) can name gates for +/// [`composite::PPVM::apply_circuit_instruction`] without depending on the ISA +/// crate directly. +pub use vihaco_circuit_isa::CircuitInstruction; + +use chumsky::Parser; +use vihaco::syntax::{ParsedModule, Resolve}; +use vihaco::{Type, Value, module::Module}; +use vihaco_parser_core::Parse; + +use crate::composite::{PPVM, PPVMDeviceInfo, PPVMInstruction}; +use crate::syntax::{PPVMHeader, PPVMResolver}; + +/// A fully resolved PPVM module, ready to load into a [`PPVM`]. +pub type PPVMModule = Module; + +/// Read a file and produce a loadable module, auto-detecting the format: a +/// leading PPVM magic is parsed as `.ssb` bytecode, otherwise as `.sst` source. +/// Mirrors [`PPVM::load_file`] but returns the module so it can be compiled once +/// and run for many shots. +pub fn load_module_file(path: &str) -> eyre::Result { + let bytes = std::fs::read(path)?; + if bytecode::is_bytecode(&bytes) { + bytecode::module_from_bytes(&bytes) + } else { + compile_program(std::str::from_utf8(&bytes)?) + } +} + +pub fn run_file(path: &str) -> eyre::Result { + let mut machine = PPVM::default(); + machine.run_file(path)?; + Ok(machine) +} + +pub fn run_program(program: &str) -> eyre::Result { + let mut machine = PPVM::default(); + machine.run_program(program)?; + Ok(machine) +} + +/// Parse `.sst` source into the unresolved AST. +pub fn parse_program(source: &str) -> eyre::Result> { + ParsedModule::::parser() + .parse(source) + .into_result() + .map_err(|errs| eyre::eyre!("parsing failed: {errs:?}")) +} + +pub fn compile_program( + source: &str, +) -> eyre::Result> { + PPVMResolver::new().resolve_module(parse_program(source)?) +} + +/// Dump `.sst` source to a `.ssb` bytecode file. +pub fn dump_program(program: &str, output_path: &str) -> eyre::Result<()> { + let bytes = bytecode::compile_to_bytes(program)?; + std::fs::write(output_path, bytes)?; + Ok(()) +} + +/// Read a `.sst` file and dump it to a `.ssb` bytecode file. +pub fn dump_file(input_path: &str, output_path: &str) -> eyre::Result<()> { + let program = std::fs::read_to_string(input_path)?; + dump_program(&program, output_path) +} + +pub mod prelude { + pub use crate::component::Circuit; + pub use crate::composite::PPVM; + pub use crate::syntax::{PPVMHeader, PPVMResolver}; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dump_program_writes_loadable_bytecode() { + let src = "device circuit.n_qubits 1;\n\ + fn @main() { const.u64 0\n circuit measure\n ret }\n"; + let path = std::env::temp_dir().join("ppvm_dump_program_test.ssb"); + dump_program(src, path.to_str().unwrap()).unwrap(); + + let mut machine = PPVM::default(); + machine.load_bytecode_file(path.to_str().unwrap()).unwrap(); + machine.run().unwrap(); + + assert_eq!(machine.measurement_record().len(), 1); + let _ = std::fs::remove_file(&path); + } + + #[test] + fn dump_file_reads_sst_and_writes_bytecode() { + let out = std::env::temp_dir().join("ppvm_dump_file_test.ssb"); + dump_file("tests/function_call.sst", out.to_str().unwrap()).unwrap(); + + let mut machine = PPVM::default(); + machine.load_bytecode_file(out.to_str().unwrap()).unwrap(); + machine.run().unwrap(); + + assert_eq!(machine.measurement_record().len(), 1); + let _ = std::fs::remove_file(&out); + } +} diff --git a/crates/ppvm-vihaco/src/measurements.rs b/crates/ppvm-vihaco/src/measurements.rs new file mode 100644 index 000000000..c5e0bf113 --- /dev/null +++ b/crates/ppvm-vihaco/src/measurements.rs @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use eyre::Result; +use smallvec::SmallVec; +use vihaco::{Effects, observe}; + +/// Measurement results are represent as an integer enum +/// 0: state |0> +/// 1: state |1> +/// 2: qubit has been lost prior to measurement +/// In byte-code, this is represented as a u32 integer, which is simpler than +/// e.g. two boolean values and matches semantics elsewhere +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum MeasurementOutcome { + Zero = 0, + One = 1, + Lost = 2, +} + +pub type MeasurementResult = SmallVec<[MeasurementOutcome; 8]>; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MeasurementEffect { + pub measurement_results: MeasurementResult, +} + +impl From> for MeasurementOutcome { + fn from(m: Option) -> Self { + match m { + Some(false) => Self::Zero, + Some(true) => Self::One, + None => Self::Lost, + } + } +} + +#[derive(Debug, Default)] +pub struct MeasurementObserver { + pub record: Vec, +} + +#[observe(MeasurementEffect)] +impl MeasurementObserver { + fn observe_measurement_effect(&mut self, effect: &MeasurementEffect) -> Result> { + self.record.push(effect.measurement_results.clone()); + Ok(Effects::none()) + } +} + +/// Per-step trace value emitted by the `Trace` instruction on the PauliSum and +/// LossyPauliSum backends. The plan's Decision 5 keeps trace and measurement +/// records as two parallel streams; this effect feeds the trace stream. +#[derive(Debug, Clone, PartialEq)] +pub struct TraceEffect { + pub value: f64, +} + +#[derive(Debug, Default)] +pub struct TraceObserver { + pub record: Vec, +} + +#[observe(TraceEffect)] +impl TraceObserver { + fn observe_trace_effect(&mut self, effect: &TraceEffect) -> Result> { + self.record.push(effect.value); + Ok(Effects::none()) + } +} + +/// Union of the two effect types a circuit instruction can produce. The plan's +/// structural note (Task 6) calls for broadening the `#[component(..., effect = +/// ...)]` annotation on `Circuit` and the executors to a union so a single +/// `Trace` (or `Measure`) instruction can fan out to the right observer. +#[derive(Debug, Clone, PartialEq)] +pub enum CircuitOutcomeEffect { + Measurement(MeasurementEffect), + Trace(TraceEffect), +} + +impl From for CircuitOutcomeEffect { + fn from(value: MeasurementEffect) -> Self { + Self::Measurement(value) + } +} + +impl From for CircuitOutcomeEffect { + fn from(value: TraceEffect) -> Self { + Self::Trace(value) + } +} diff --git a/crates/ppvm-vihaco/src/observable.rs b/crates/ppvm-vihaco/src/observable.rs new file mode 100644 index 000000000..f5bc92032 --- /dev/null +++ b/crates/ppvm-vihaco/src/observable.rs @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Parse the `device circuit.observable` header value into Pauli-sum terms. +//! +//! Lives in `ppvm-vihaco` rather than `ppvm-runtime` because it only parses +//! header syntax that `ppvm-vihaco` consumes; `ppvm-runtime` has no notion of +//! a textual observable. The unrelated `PauliPattern::parse` in +//! `ppvm-runtime` is a *matcher* grammar (alternation, star, positional +//! anchors) and shares only the `I/X/Y/Z` alphabet with this one. +//! +//! Grammar (informal): +//! ```text +//! sum = WS* term (WS* sign WS* term)* WS* +//! term = coefficient (WS* '*')? WS* pauli_word +//! | pauli_word +//! sign = '+' | '-' +//! coefficient +//! = digits ('.' digits?)? ([eE] [+-]? digits)? +//! | '.' digits ([eE] [+-]? digits)? +//! pauli_word +//! = [IXYZ]{n_qubits} +//! ``` +//! +//! - The first term may have a leading `+` or `-` (no sign means `+`). +//! - Absent coefficient defaults to `1.0`. +//! - The `*` is only legal *after* a coefficient — bare `*ZZ` is rejected. +//! - The word must be exactly `n_qubits` characters from `I/X/Y/Z`. +//! +//! Rejected at parse time: +//! - Empty or whitespace-only input. +//! - Bare coefficients with no Pauli word. +//! - Words shorter or longer than `n_qubits`. +//! - Invalid Pauli characters. +//! - Missing `+`/`-` between terms. + +use chumsky::error::Simple; +use chumsky::extra; +use chumsky::prelude::*; +use eyre::{Result, eyre}; + +type Err<'src> = extra::Err>; + +/// Parse a Pauli-sum string like `"1.0*ZZ + 0.5*XX - 0.3*YY"` into a list of +/// `(word_source, coefficient)` pairs. Callers convert the word source to +/// their preferred Pauli-word type via `PauliWord::from` / `LossyPauliWord::from`. +pub fn parse_pauli_sum_terms(input: &str, n_qubits: usize) -> Result> { + if n_qubits == 0 { + return Err(eyre!("n_qubits must be at least 1")); + } + if input.trim().is_empty() { + return Err(eyre!("empty Pauli-sum input")); + } + pauli_sum_parser(n_qubits) + .parse(input) + .into_result() + .map_err(|errs| { + let joined = errs + .into_iter() + .map(|e| e.to_string()) + .collect::>() + .join("; "); + eyre!("invalid Pauli-sum `{input}`: {joined}") + }) +} + +fn pauli_sum_parser<'src>( + n_qubits: usize, +) -> impl Parser<'src, &'src str, Vec<(String, f64)>, Err<'src>> { + // Numeric literal: digits[.digits?]?[exp]? or .digits[exp]? + let digits = text::digits(10).at_least(1); + let exp = one_of("eE") + .then(one_of("+-").or_not()) + .then(digits) + .ignored(); + let mantissa = choice(( + digits + .then(just('.').then(digits.or_not()).or_not()) + .ignored(), + just('.').then(digits).ignored(), + )); + let coefficient = mantissa.then(exp.or_not()).to_slice().map(|s: &str| { + s.parse::() + .expect("mantissa+exponent grammar validates parseability") + }); + + // Pauli word: exactly n_qubits chars from IXYZ. + let pauli_word = one_of("IXYZ") + .repeated() + .exactly(n_qubits) + .collect::(); + + // Term: coefficient [*] word | bare word. + let term_with_coeff = coefficient + .then_ignore(just('*').padded().or_not()) + .then(pauli_word) + .map(|(c, w)| (w, c)); + let term_bare = pauli_word.map(|w| (w, 1.0)); + let term = choice((term_with_coeff, term_bare)); + + // Sign factor: +1 / -1. + let sign = choice((just('+').to(1.0_f64), just('-').to(-1.0_f64))); + + // First term: optional leading sign. + let first = sign + .padded() + .or_not() + .then(term) + .map(|(s, (w, c))| (w, s.unwrap_or(1.0) * c)); + + // Subsequent terms: required + or - before the term. + let rest = sign.padded().then(term).map(|(s, (w, c))| (w, s * c)); + + let inner = first + .then(rest.repeated().collect::>()) + .map(|(first, mut rest)| { + rest.insert(0, first); + rest + }); + + inner.padded().then_ignore(end()) +} + +#[cfg(test)] +mod tests { + use super::*; + + // ─── Happy paths ──────────────────────────────────────────────────── + + #[test] + fn single_term_no_coefficient() { + let got = parse_pauli_sum_terms("ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0)]); + } + + #[test] + fn single_term_with_explicit_star() { + let got = parse_pauli_sum_terms("0.5*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 0.5)]); + } + + #[test] + fn single_term_without_star() { + let got = parse_pauli_sum_terms("0.5ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 0.5)]); + } + + #[test] + fn integer_coefficient() { + let got = parse_pauli_sum_terms("3XY", 2).unwrap(); + assert_eq!(got, vec![("XY".to_string(), 3.0)]); + } + + #[test] + fn leading_negative_no_coefficient() { + let got = parse_pauli_sum_terms("-ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), -1.0)]); + } + + #[test] + fn leading_negative_with_coefficient() { + let got = parse_pauli_sum_terms("-0.5*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), -0.5)]); + } + + #[test] + fn leading_plus_no_coefficient() { + let got = parse_pauli_sum_terms("+ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0)]); + } + + #[test] + fn multiple_terms_with_addition() { + let got = parse_pauli_sum_terms("1.0*ZZ + 0.5*XX", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0), ("XX".to_string(), 0.5)]); + } + + #[test] + fn multiple_terms_with_subtraction() { + let got = parse_pauli_sum_terms("ZZ - XX", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0), ("XX".to_string(), -1.0)]); + } + + #[test] + fn three_term_mixed() { + let got = parse_pauli_sum_terms("1.0*ZZ + 0.5*XX - 0.3*YY", 2).unwrap(); + assert_eq!( + got, + vec![ + ("ZZ".to_string(), 1.0), + ("XX".to_string(), 0.5), + ("YY".to_string(), -0.3), + ] + ); + } + + #[test] + fn scientific_notation_coefficient() { + let got = parse_pauli_sum_terms("1e-3*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1e-3)]); + } + + #[test] + fn unsigned_positive_exponent() { + let got = parse_pauli_sum_terms("1e3*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1000.0)]); + } + + #[test] + fn uppercase_exponent_with_explicit_sign() { + let got = parse_pauli_sum_terms("2.5E+2*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 250.0)]); + } + + #[test] + fn coefficient_with_trailing_dot() { + let got = parse_pauli_sum_terms("1.*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0)]); + } + + #[test] + fn whitespace_tolerance_aggressive() { + let got = parse_pauli_sum_terms(" 1.0 * ZZ + 0.5 * XX ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 1.0), ("XX".to_string(), 0.5)]); + } + + #[test] + fn coefficient_starts_with_dot() { + let got = parse_pauli_sum_terms(".5*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 0.5)]); + } + + #[test] + fn identity_in_word_is_valid() { + let got = parse_pauli_sum_terms("IZIZ", 4).unwrap(); + assert_eq!(got, vec![("IZIZ".to_string(), 1.0)]); + } + + #[test] + fn single_qubit_n_equals_one() { + let got = parse_pauli_sum_terms("0.5*X + Y - 0.25Z", 1).unwrap(); + assert_eq!( + got, + vec![ + ("X".to_string(), 0.5), + ("Y".to_string(), 1.0), + ("Z".to_string(), -0.25), + ] + ); + } + + #[test] + fn duplicate_word_emits_two_terms() { + // Coefficient collapse is the caller's job (via `PauliSum +=`); the + // parser stays a thin syntactic layer. + let got = parse_pauli_sum_terms("0.5*ZZ + 0.5*ZZ", 2).unwrap(); + assert_eq!(got, vec![("ZZ".to_string(), 0.5), ("ZZ".to_string(), 0.5)]); + } + + // ─── Error paths ──────────────────────────────────────────────────── + + #[test] + fn rejects_empty_input() { + let err = parse_pauli_sum_terms("", 2).unwrap_err(); + assert!(err.to_string().contains("empty"), "{err}"); + } + + #[test] + fn rejects_whitespace_only() { + let err = parse_pauli_sum_terms(" \t \n ", 2).unwrap_err(); + assert!(err.to_string().contains("empty"), "{err}"); + } + + #[test] + fn rejects_zero_qubits() { + let err = parse_pauli_sum_terms("ZZ", 0).unwrap_err(); + assert!(err.to_string().contains("n_qubits"), "{err}"); + } + + #[test] + fn rejects_bare_coefficient() { + let err = parse_pauli_sum_terms("0.5", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_bare_sign() { + let err = parse_pauli_sum_terms("+", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_short_word() { + let err = parse_pauli_sum_terms("Z", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_long_word() { + let err = parse_pauli_sum_terms("ZZZ", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_invalid_pauli_character() { + let err = parse_pauli_sum_terms("ZF", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_lowercase_pauli_character() { + let err = parse_pauli_sum_terms("zz", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_missing_separator_between_terms() { + let err = parse_pauli_sum_terms("ZZ XX", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_double_sign() { + let err = parse_pauli_sum_terms("++ZZ", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_star_without_coefficient() { + // Bare `*ZZ` is rejected — `*` is only legal after a coefficient. + let err = parse_pauli_sum_terms("*ZZ", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_trailing_garbage() { + let err = parse_pauli_sum_terms("ZZ garbage", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } + + #[test] + fn rejects_trailing_garbage_after_complete_sum() { + // Distinct from `rejects_missing_separator_between_terms`: here a + // complete two-term sum parses successfully and the failure is the + // `end()` check after the last term. + let err = parse_pauli_sum_terms("ZZ + XX trailing", 2).unwrap_err(); + assert!(err.to_string().contains("invalid Pauli-sum"), "{err}"); + } +} diff --git a/crates/ppvm-vihaco/src/shots.rs b/crates/ppvm-vihaco/src/shots.rs new file mode 100644 index 000000000..b1935382f --- /dev/null +++ b/crates/ppvm-vihaco/src/shots.rs @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Running a compiled program for many shots, optionally across threads. +//! +//! Each shot runs on a fresh [`PPVM`] so shots are fully independent; the +//! module is compiled once and shared. With the `rayon` feature, [`run_shots`] +//! parallelizes across shots when the global pool (sized once by +//! [`set_global_threads`]) has more than one thread and there are enough shots +//! to amortize the overhead. + +use crate::PPVMModule; +use crate::composite::PPVM; +use crate::measurements::MeasurementResult; + +/// One shot's full output: the measurement record and the trace-instruction +/// record. Either may be empty depending on what the program emits. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct ShotRecord { + pub measurements: Vec, + pub traces: Vec, +} + +/// Below this many shots, parallelism's overhead outweighs its benefit and we +/// always run serially. Provisional — tune with benchmarks. +pub const PARALLEL_SHOT_THRESHOLD: usize = 128; + +/// Per-shot seed derived from the base seed and the shot index, so every shot +/// gets a distinct RNG stream (a shared seed would make all shots identical). +/// Depends only on `(base, index)`, so serial and parallel runs are bit-for-bit +/// identical for a given seed regardless of thread count. +fn shot_seed(base: Option, index: usize) -> Option { + base.map(|b| b.wrapping_add(index as u64)) +} + +/// Run a single shot on a fresh machine and return both records. +fn run_one_shot(module: &PPVMModule, seed: Option) -> eyre::Result { + let mut machine = PPVM::default(); + machine.load(module)?; + machine.run_with_seed(seed)?; + Ok(ShotRecord { + measurements: machine.measurement_record(), + traces: machine.trace_record(), + }) +} + +/// Run `shots` shots serially. One entry per shot, in order. +pub fn run_shots_serial( + module: &PPVMModule, + shots: usize, + seed: Option, +) -> eyre::Result> { + (0..shots) + .map(|i| run_one_shot(module, shot_seed(seed, i))) + .collect() +} + +/// Run `shots` shots across the global rayon pool. One entry per shot, in order +/// (preserved by the indexed parallel iterator). The pool size is whatever +/// [`set_global_threads`] configured at startup; each shot runs on a worker +/// thread, so the intra-shot parallelism guard keeps a single shot serial and +/// the pool is never oversubscribed. +#[cfg(feature = "rayon")] +pub fn run_shots_parallel( + module: &PPVMModule, + shots: usize, + seed: Option, +) -> eyre::Result> { + use rayon::prelude::*; + + (0..shots) + .into_par_iter() + .map(|i| run_one_shot(module, shot_seed(seed, i))) + .collect() +} + +/// Decide whether to spread shots across the rayon pool. Worth it only with a +/// multi-thread pool and enough shots to amortize the overhead; a single-thread +/// pool always takes the serial path, keeping results deterministic. +#[cfg(feature = "rayon")] +fn should_parallelize(num_threads: usize, shots: usize) -> bool { + num_threads > 1 && shots >= PARALLEL_SHOT_THRESHOLD +} + +/// Run `shots` shots, choosing serial or parallel execution. Goes parallel only +/// when built with `rayon`, the global pool has more than one thread, and there +/// are enough shots to be worth it; otherwise runs serially. The pool size is +/// set once at startup by [`set_global_threads`]. +pub fn run_shots( + module: &PPVMModule, + shots: usize, + seed: Option, +) -> eyre::Result> { + #[cfg(feature = "rayon")] + if should_parallelize(rayon::current_num_threads(), shots) { + return run_shots_parallel(module, shots, seed); + } + + run_shots_serial(module, shots, seed) +} + +/// Configure the process-wide rayon thread pool. Call once, before any parallel +/// work runs. A count of `1` forces fully serial, deterministic execution — both +/// across shots and within a single machine's coefficient propagation. +#[cfg(feature = "rayon")] +pub fn set_global_threads(threads: usize) -> eyre::Result<()> { + rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .build_global()?; + Ok(()) +} + +/// Without the `rayon` feature there is no pool to size; anything but a single +/// thread is meaningless, so reject it rather than silently run serially. +#[cfg(not(feature = "rayon"))] +pub fn set_global_threads(threads: usize) -> eyre::Result<()> { + if threads > 1 { + eyre::bail!("this build has no parallelism support; --threads must be 1"); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::compile_program; + use crate::measurements::MeasurementOutcome; + + /// Measures q0 in |0>: every shot is deterministically `0`. + const DETERMINISTIC: &str = + "device circuit.n_qubits 1;\nfn @main() { const.u64 0\n circuit measure\n ret }\n"; + + /// Prepares |+> with H, then measures q0: each shot is a random 0/1. + const RANDOM: &str = "device circuit.n_qubits 1;\nfn @main() { const.u64 0\n circuit h\n const.u64 0\n circuit measure\n ret }\n"; + + fn module(src: &str) -> PPVMModule { + compile_program(src).unwrap() + } + + #[test] + fn serial_runs_one_record_per_shot() { + let m = module(DETERMINISTIC); + let records = run_shots_serial(&m, 5, None).unwrap(); + assert_eq!(records.len(), 5); + for shot in &records { + // One measurement event, one qubit, deterministically |0>. + assert_eq!(shot.measurements.len(), 1); + assert_eq!(shot.measurements[0].as_slice(), [MeasurementOutcome::Zero]); + assert!(shot.traces.is_empty(), "Tableau backend emits no traces"); + } + } + + #[test] + fn dispatcher_runs_all_shots() { + let m = module(DETERMINISTIC); + // 10 shots is below the parallel threshold, so this takes the serial path. + let records = run_shots(&m, 10, None).unwrap(); + assert_eq!(records.len(), 10); + } + + #[test] + fn same_seed_is_reproducible() { + let m = module(RANDOM); + let a = run_shots_serial(&m, 20, Some(42)).unwrap(); + let b = run_shots_serial(&m, 20, Some(42)).unwrap(); + assert_eq!(a, b); + } + + #[test] + fn per_shot_seeds_differ() { + // If every shot shared one seed, all 20 outcomes would be identical. + // Distinct per-shot seeds must produce a mix of 0s and 1s. + let m = module(RANDOM); + let records = run_shots_serial(&m, 20, Some(42)).unwrap(); + let first = &records[0]; + assert!( + records.iter().any(|r| r != first), + "expected varied outcomes across shots, got {records:?}" + ); + } + + #[cfg(feature = "rayon")] + #[test] + fn serial_and_parallel_match_for_same_seed() { + let m = module(RANDOM); + let serial = run_shots_serial(&m, 64, Some(7)).unwrap(); + let parallel = run_shots_parallel(&m, 64, Some(7)).unwrap(); + assert_eq!(serial, parallel); + } + + #[cfg(feature = "rayon")] + #[test] + fn parallelizes_only_with_multiple_threads_above_threshold() { + // A single-thread pool is always serial, no matter how many shots. + assert!(!should_parallelize(1, 100_000)); + // Multiple threads, but too few shots to amortize the overhead: serial. + assert!(!should_parallelize(8, PARALLEL_SHOT_THRESHOLD - 1)); + // Multiple threads at the threshold: parallel. + assert!(should_parallelize(8, PARALLEL_SHOT_THRESHOLD)); + // Multiple threads, plenty of shots: parallel. + assert!(should_parallelize(8, 100_000)); + } +} diff --git a/crates/ppvm-vihaco/src/syntax.rs b/crates/ppvm-vihaco/src/syntax.rs new file mode 100644 index 000000000..96a7d03e5 --- /dev/null +++ b/crates/ppvm-vihaco/src/syntax.rs @@ -0,0 +1,725 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use chumsky::{Parser, error::Simple, extra}; +use vihaco::{ + Type, Value, + module::Module, + syntax::{BodyItem, RawForm, RawOperand, Resolve}, +}; +use vihaco_circuit_isa::CircuitInstruction; +use vihaco_parser_core::Parse; + +use crate::composite::{BackendKind, PPVMDeviceInfo, PPVMInstruction}; + +#[derive(Debug, Clone, PartialEq, vihaco_parser::Parse)] +#[head = "device "] +pub enum PPVMHeader { + #[token = "circuit.n_qubits"] + #[delimiters(open = "", close = "", separator = "")] + NumQubits(usize), + + #[token = "circuit.coefficient_threshold"] + #[delimiters(open = "", close = "", separator = "")] + CoefficientThrehsold(f64), + + #[token = "circuit.backend"] + #[delimiters(open = "", close = "", separator = "")] + Backend(BackendKind), + + #[token = "circuit.observable"] + #[delimiters(open = "", close = "", separator = "")] + Observable(#[parse_with = "vihaco_parser_core::ident"] String), + + #[token = "circuit.max_pauli_weight"] + #[delimiters(open = "", close = "", separator = "")] + MaxPauliWeight(usize), +} + +#[derive(Debug, Default)] +pub struct PPVMResolver { + strings: Vec, +} + +impl PPVMResolver { + pub fn new() -> Self { + Self::default() + } + + fn apply_header(info: &mut PPVMDeviceInfo, header: PPVMHeader) -> eyre::Result<()> { + match header { + PPVMHeader::NumQubits(n) => { + info.n_qubits = n; + } + PPVMHeader::CoefficientThrehsold(t) => { + info.coefficient_threshold = t; + } + PPVMHeader::Backend(b) => { + info.backend = b; + } + PPVMHeader::Observable(s) => { + info.observable = Some(s); + } + PPVMHeader::MaxPauliWeight(w) => { + info.max_pauli_weight = Some(w); + } + } + Ok(()) + } + + fn lower_raw(&mut self, raw: RawForm) -> eyre::Result> { + match raw.mnemonic.as_str() { + "ret" => { + let keep = match raw.operands.as_slice() { + [] => 0u32, + [RawOperand::UInt(n)] => u32::try_from(*n) + .map_err(|_| eyre::eyre!("`ret` keep count {n} does not fit in u32"))?, + other => { + return Err(eyre::eyre!( + "`ret` takes 0 or 1 unsigned int operands, got {other:?}" + )); + } + }; + Ok(vec![vihaco_cpu::Instruction::Return(keep).into()]) + } + "const.str" => { + let lit = match raw.operands.as_slice() { + [RawOperand::StringLit(s)] => s.clone(), + other => { + return Err(eyre::eyre!( + "`const.str` takes one string literal, got {other:?}" + )); + } + }; + let addr = u32::try_from(self.strings.len()).map_err(|_| { + eyre::eyre!("string table overflowed u32 at `const.str` lowering") + })?; + self.strings.push(lit); + Ok(vec![ + vihaco_cpu::Instruction::Const(vihaco::Value::String(addr)).into(), + ]) + } + other => Err(eyre::eyre!( + "PPVMResolver: unhandled raw form `{other}` (operands: {:?})", + raw.operands + )), + } + } +} + +impl Resolve for PPVMResolver { + type Module = Module; + fn resolve_module( + &mut self, + parsed: vihaco::syntax::ParsedModule, + ) -> eyre::Result { + let mut info = PPVMDeviceInfo::default(); + for header in parsed.headers { + Self::apply_header(&mut info, header)?; + } + + let mut code: Vec = Vec::new(); + let mut labels: HashMap = HashMap::new(); + let mut branch_patches: Vec<(usize, BranchPatch)> = Vec::new(); + let mut call_patches: Vec<(usize, CallPatch)> = Vec::new(); + for function in parsed.functions { + if labels + .insert(function.name.clone(), code.len() as u32) + .is_some() + { + return Err(eyre::eyre!("duplicate function name `@{}`", function.name)); + } + for item in function.body { + match item { + BodyItem::Direct(inst) => code.push(inst), + BodyItem::Raw(raw) => { + if let Some(name) = raw_as_label(&raw) { + if labels.insert(name.clone(), code.len() as u32).is_some() { + return Err(eyre::eyre!("duplicate label `@{name}`")); + } + continue; + } + if let Some(patch) = raw_as_branch(&raw) { + let idx = code.len(); + code.push(patch.placeholder()); + branch_patches.push((idx, patch)); + continue; + } + if let Some(patch) = raw_as_call(&raw)? { + let idx = code.len(); + code.push(patch.placeholder()); + call_patches.push((idx, patch)); + continue; + } + code.extend(self.lower_raw(raw)?); + } + } + } + } + for (idx, patch) in branch_patches { + patch.apply(&mut code, idx, &labels)?; + } + for (idx, patch) in call_patches { + patch.apply(&mut code, idx, &labels)?; + } + + let strings = std::mem::take(&mut self.strings); + let module = Module { + code, + strings, + extra: info, + ..Default::default() + }; + Ok(module) + } +} + +type Err<'src> = extra::Err>; + +impl<'src> Parse<'src> for PPVMInstruction { + fn parser() -> impl Parser<'src, &'src str, Self, Err<'src>> { + use chumsky::prelude::*; + + let cpu = ::parser().map(PPVMInstruction::Cpu); + + // Reuse the derived parser for all CircuitInstruction variants, + // gated behind the `circuit ` keyword (covers gates, noise channels, + // measure/reset, trace, and truncate — i.e. everything circuit-side). + let circuit = just("circuit") + .then(text::whitespace().at_least(1)) + .ignore_then(::parser()) + .map(PPVMInstruction::Circuit); + + // Try `circuit ...` first so CPU doesn't see "circuit" as an identifier. + choice((circuit, cpu)) + } +} + +// ---- Everything below is 1:1 copy from Acamar with Acamar -> PPVM renaming ---- + +/// A deferred branch whose target(s) couldn't be resolved at lowering time +/// because the label may appear later in the function body. Patched in a +/// second pass once all labels are known. +#[derive(Debug)] +enum BranchPatch { + /// `br @target` — fills the `u32` in `cpu::Instruction::Branch`. + Unconditional(String), + /// `br @t, @f` / `cond_br @t, @f` — fills both `u32`s in + /// `cpu::Instruction::ConditionalBranch`. + Conditional(String, String), +} + +/// `@name:` → `Some("name")`. Body parser already emits `@entry:` as a single +/// raw mnemonic with no operands, so the check is purely on the mnemonic +/// shape. +fn raw_as_label(raw: &RawForm) -> Option { + if !raw.operands.is_empty() { + return None; + } + let m = raw.mnemonic.as_str(); + let stripped = m.strip_prefix('@')?.strip_suffix(':')?; + if stripped.is_empty() { + return None; + } + Some(stripped.to_string()) +} + +/// `br @t` / `br @t, @f` / `cond_br @t, @f`. +fn raw_as_branch(raw: &RawForm) -> Option { + let symbols: Vec<&str> = raw + .operands + .iter() + .map(|op| match op { + RawOperand::Symbol(s) => Some(s.as_str()), + _ => None, + }) + .collect::>>()?; + + match (raw.mnemonic.as_str(), symbols.as_slice()) { + ("br", [t]) => Some(BranchPatch::Unconditional((*t).to_string())), + ("br", [t, f]) | ("cond_br", [t, f]) => { + Some(BranchPatch::Conditional((*t).to_string(), (*f).to_string())) + } + _ => None, + } +} + +impl BranchPatch { + fn placeholder(&self) -> PPVMInstruction { + match self { + BranchPatch::Unconditional(_) => vihaco_cpu::Instruction::Branch(u32::MAX).into(), + BranchPatch::Conditional(_, _) => { + vihaco_cpu::Instruction::ConditionalBranch(u32::MAX, u32::MAX).into() + } + } + } + + fn apply( + self, + code: &mut [PPVMInstruction], + idx: usize, + labels: &HashMap, + ) -> eyre::Result<()> { + let lookup = |name: &str| { + labels + .get(name) + .copied() + .ok_or_else(|| eyre::eyre!("undefined label `@{name}`")) + }; + let resolved = match self { + BranchPatch::Unconditional(t) => vihaco_cpu::Instruction::Branch(lookup(&t)?).into(), + BranchPatch::Conditional(t, f) => { + vihaco_cpu::Instruction::ConditionalBranch(lookup(&t)?, lookup(&f)?).into() + } + }; + code[idx] = resolved; + Ok(()) + } +} + +/// `call , @target` — symbolic target resolved in a second pass against +/// the same label table that holds branch targets and function entry points. +#[derive(Debug)] +struct CallPatch { + arity: u32, + target: String, +} + +/// `call , @target` → `Some(CallPatch)`. Returns `Ok(None)` for any +/// other mnemonic so the resolver can fall through to `lower_raw`. +fn raw_as_call(raw: &RawForm) -> eyre::Result> { + if raw.mnemonic != "call" { + return Ok(None); + } + match raw.operands.as_slice() { + [RawOperand::UInt(arity), RawOperand::Symbol(target)] => { + let arity = u32::try_from(*arity) + .map_err(|_| eyre::eyre!("`call` arity {arity} does not fit in u32"))?; + Ok(Some(CallPatch { + arity, + target: target.clone(), + })) + } + other => Err(eyre::eyre!( + "`call` expects `, @`, got operands {other:?}" + )), + } +} + +impl CallPatch { + fn placeholder(&self) -> PPVMInstruction { + vihaco_cpu::Instruction::Call(self.arity, u32::MAX).into() + } + + fn apply( + self, + code: &mut [PPVMInstruction], + idx: usize, + labels: &HashMap, + ) -> eyre::Result<()> { + let target = labels + .get(&self.target) + .copied() + .ok_or_else(|| eyre::eyre!("undefined function `@{}`", self.target))?; + code[idx] = vihaco_cpu::Instruction::Call(self.arity, target).into(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use vihaco::syntax::ParsedModule; + + fn parse_module(source: &str) -> ParsedModule { + ParsedModule::::parser() + .parse(source) + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")) + } + + fn raw(mnemonic: &str, operands: Vec) -> RawForm { + RawForm { + mnemonic: mnemonic.to_string(), + operands, + } + } + + // ─── Header parsing ─────────────────────────────────────────────────── + + #[test] + fn header_parses_n_qubits() { + let got = ::parser() + .parse("device circuit.n_qubits 5") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::NumQubits(5)); + } + + #[test] + fn header_parses_coefficient_threshold() { + let got = ::parser() + .parse("device circuit.coefficient_threshold 1e-10") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::CoefficientThrehsold(1e-10)); + } + + #[test] + fn header_n_qubits_rejects_extra_operand() { + // The variant has exactly one field, so the parser consumes one + // integer. Wrapped in a full module, a second integer must trip the + // module-level parser. + let result = ParsedModule::::parser() + .parse( + "device circuit.n_qubits 5 6;\n\ + fn @main() { ret }\n", + ) + .into_result(); + assert!(result.is_err(), "expected parse error, got {result:?}"); + } + + #[test] + fn header_coefficient_threshold_rejects_extra_operand() { + let result = ParsedModule::::parser() + .parse( + "device circuit.coefficient_threshold 1e-10 0.5;\n\ + fn @main() { ret }\n", + ) + .into_result(); + assert!(result.is_err(), "expected parse error, got {result:?}"); + } + + #[test] + fn header_parses_backend_tableau() { + let got = ::parser() + .parse("device circuit.backend tableau") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::Backend(BackendKind::Tableau)); + } + + #[test] + fn header_parses_backend_paulisum() { + let got = ::parser() + .parse("device circuit.backend paulisum") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::Backend(BackendKind::PauliSum)); + } + + #[test] + fn header_parses_backend_lossy_paulisum() { + let got = ::parser() + .parse("device circuit.backend lossy_paulisum") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::Backend(BackendKind::LossyPauliSum)); + } + + #[test] + fn header_parses_observable_single_pauli_word() { + let got = ::parser() + .parse("device circuit.observable ZZIIII") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::Observable("ZZIIII".to_string())); + } + + #[test] + fn header_parses_max_pauli_weight() { + let got = ::parser() + .parse("device circuit.max_pauli_weight 8") + .into_result() + .unwrap_or_else(|e| panic!("parse failed: {e:?}")); + assert_eq!(got, PPVMHeader::MaxPauliWeight(8)); + } + + #[test] + fn apply_header_sets_n_qubits() { + let mut info = PPVMDeviceInfo::default(); + PPVMResolver::apply_header(&mut info, PPVMHeader::NumQubits(7)).unwrap(); + assert_eq!(info.n_qubits, 7); + } + + #[test] + fn apply_header_sets_coefficient_threshold() { + let mut info = PPVMDeviceInfo::default(); + PPVMResolver::apply_header(&mut info, PPVMHeader::CoefficientThrehsold(5e-6)).unwrap(); + assert_eq!(info.coefficient_threshold, 5e-6); + } + + #[test] + fn apply_header_sets_backend() { + let mut info = PPVMDeviceInfo::default(); + PPVMResolver::apply_header(&mut info, PPVMHeader::Backend(BackendKind::PauliSum)).unwrap(); + assert_eq!(info.backend, BackendKind::PauliSum); + } + + #[test] + fn apply_header_sets_observable() { + let mut info = PPVMDeviceInfo::default(); + PPVMResolver::apply_header(&mut info, PPVMHeader::Observable("ZZ".to_string())).unwrap(); + assert_eq!(info.observable.as_deref(), Some("ZZ")); + } + + #[test] + fn apply_header_sets_max_pauli_weight() { + let mut info = PPVMDeviceInfo::default(); + PPVMResolver::apply_header(&mut info, PPVMHeader::MaxPauliWeight(4)).unwrap(); + assert_eq!(info.max_pauli_weight, Some(4)); + } + + #[test] + fn device_info_defaults_match_tableau_no_observable_no_truncation() { + let info = PPVMDeviceInfo::default(); + assert_eq!(info.backend, BackendKind::Tableau); + assert_eq!(info.observable, None); + assert_eq!(info.max_pauli_weight, None); + } + + // ─── PPVMInstruction parser dispatch ────────────────────────────────── + + #[test] + fn ppvm_instruction_parses_cpu_const() { + let got = ::parser() + .parse("const.u64 7") + .into_result() + .unwrap(); + assert!(matches!( + got, + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Const(Value::U64(7))) + )); + } + + #[test] + fn ppvm_instruction_parses_gate_h() { + let got = ::parser() + .parse("circuit h") + .into_result() + .unwrap(); + assert!(matches!( + got, + PPVMInstruction::Circuit(CircuitInstruction::H) + )); + } + + #[test] + fn ppvm_instruction_parses_gate_cnot() { + let got = ::parser() + .parse("circuit cnot") + .into_result() + .unwrap(); + assert!(matches!( + got, + PPVMInstruction::Circuit(CircuitInstruction::CNOT) + )); + } + + #[test] + fn ppvm_instruction_parses_gate_measure() { + let got = ::parser() + .parse("circuit measure") + .into_result() + .unwrap(); + assert!(matches!( + got, + PPVMInstruction::Circuit(CircuitInstruction::Measure) + )); + } + + #[test] + fn ppvm_instruction_parses_gate_rx() { + let got = ::parser() + .parse("circuit rx") + .into_result() + .unwrap(); + assert!(matches!( + got, + PPVMInstruction::Circuit(CircuitInstruction::RX) + )); + } + + #[test] + fn ppvm_instruction_rejects_bare_circuit_token_without_circuit_prefix() { + // `h` on its own must not parse as Circuit(H) — only `circuit h` does. + // Without `circuit `, the CPU parser is tried, which should reject + // `h` (not a CPU mnemonic). + let result = ::parser() + .parse("h") + .into_result(); + assert!(result.is_err(), "expected parse error, got {result:?}"); + } + + // ─── lower_raw ──────────────────────────────────────────────────────── + + #[test] + fn lower_raw_ret_emits_return_zero() { + let mut r = PPVMResolver::new(); + let out = r.lower_raw(raw("ret", vec![])).unwrap(); + assert_eq!(out.len(), 1); + assert!(matches!( + out[0], + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Return(0)) + )); + } + + #[test] + fn lower_raw_ret_with_uint_operand_emits_return_n() { + let mut r = PPVMResolver::new(); + let out = r.lower_raw(raw("ret", vec![RawOperand::UInt(2)])).unwrap(); + assert_eq!(out.len(), 1); + assert!(matches!( + out[0], + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Return(2)) + )); + } + + #[test] + fn lower_raw_ret_with_non_uint_operand_errors() { + let mut r = PPVMResolver::new(); + let err = r + .lower_raw(raw("ret", vec![RawOperand::Symbol("foo".into())])) + .unwrap_err(); + assert!( + err.to_string().contains("`ret` takes 0 or 1 unsigned int"), + "err: {err}" + ); + } + + #[test] + fn lower_raw_unknown_mnemonic_errors() { + let mut r = PPVMResolver::new(); + let err = r.lower_raw(raw("nope", vec![])).unwrap_err(); + assert!(err.to_string().contains("unhandled raw form"), "err: {err}"); + } + + // ─── End-to-end resolver behaviour ──────────────────────────────────── + + #[test] + fn resolver_populates_device_info_from_headers() { + let parsed = parse_module( + "device circuit.n_qubits 3;\n\ + device circuit.coefficient_threshold 1e-8;\n\ + fn @main() { ret }\n", + ); + let m = PPVMResolver::new().resolve_module(parsed).unwrap(); + assert_eq!(m.extra.n_qubits, 3); + assert_eq!(m.extra.coefficient_threshold, 1e-8); + } + + #[test] + fn resolver_populates_paulisum_headers() { + let parsed = parse_module( + "device circuit.n_qubits 4;\n\ + device circuit.backend paulisum;\n\ + device circuit.observable ZZII;\n\ + device circuit.max_pauli_weight 8;\n\ + fn @main() { ret }\n", + ); + let m = PPVMResolver::new().resolve_module(parsed).unwrap(); + assert_eq!(m.extra.n_qubits, 4); + assert_eq!(m.extra.backend, BackendKind::PauliSum); + assert_eq!(m.extra.observable.as_deref(), Some("ZZII")); + assert_eq!(m.extra.max_pauli_weight, Some(8)); + } + + #[test] + fn resolver_lowers_simple_bell_body() { + // Smoke test the whole pipeline on a tiny bell-like body. + let parsed = parse_module( + "device circuit.n_qubits 2;\n\ + fn @main() {\n\ + const.u64 0\n\ + circuit h\n\ + const.u64 0\n\ + const.u64 1\n\ + circuit cnot\n\ + ret\n\ + }\n", + ); + let m = PPVMResolver::new().resolve_module(parsed).unwrap(); + // const.u64 0 / circuit h / const.u64 0 / const.u64 1 / circuit cnot / ret + assert_eq!(m.code.len(), 6); + assert!(matches!( + m.code[1], + PPVMInstruction::Circuit(CircuitInstruction::H) + )); + assert!(matches!( + m.code[4], + PPVMInstruction::Circuit(CircuitInstruction::CNOT) + )); + assert!(matches!( + m.code[5], + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Return(0)) + )); + } + + #[test] + fn resolver_resolves_forward_branch_targets() { + let parsed = parse_module( + "fn @main() {\n\ + @loop:\n\ + br @done\n\ + @done:\n\ + ret\n\ + }\n", + ); + let m = PPVMResolver::new().resolve_module(parsed).unwrap(); + assert!(matches!( + m.code[0], + PPVMInstruction::Cpu(vihaco_cpu::Instruction::Branch(1)) + )); + } + + #[test] + fn resolver_resolves_conditional_branch_with_two_targets() { + let parsed = parse_module( + "fn @main() {\n\ + @head:\n\ + br @head, @exit\n\ + @exit:\n\ + ret\n\ + }\n", + ); + let m = PPVMResolver::new().resolve_module(parsed).unwrap(); + assert!(matches!( + m.code[0], + PPVMInstruction::Cpu(vihaco_cpu::Instruction::ConditionalBranch(0, 1)) + )); + } + + #[test] + fn resolver_rejects_undefined_branch_target() { + let parsed = parse_module( + "fn @main() {\n\ + br @missing\n\ + ret\n\ + }\n", + ); + let err = PPVMResolver::new().resolve_module(parsed).unwrap_err(); + assert!( + err.to_string().contains("undefined label `@missing`"), + "err: {err}" + ); + } + + #[test] + fn resolver_rejects_duplicate_label() { + let parsed = parse_module( + "fn @main() {\n\ + @same:\n\ + ret\n\ + @same:\n\ + ret\n\ + }\n", + ); + let err = PPVMResolver::new().resolve_module(parsed).unwrap_err(); + assert!( + err.to_string().contains("duplicate label `@same`"), + "err: {err}" + ); + } +} diff --git a/crates/ppvm-vihaco/tests/bell.sst b/crates/ppvm-vihaco/tests/bell.sst new file mode 100644 index 000000000..df2487bdb --- /dev/null +++ b/crates/ppvm-vihaco/tests/bell.sst @@ -0,0 +1,18 @@ +device circuit.n_qubits 2; + +fn @main() { + const.u64 0 + circuit h + + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 0 + circuit measure + + const.u64 0 + circuit measure + + ret +} diff --git a/crates/ppvm-vihaco/tests/branch_on_outcome.sst b/crates/ppvm-vihaco/tests/branch_on_outcome.sst new file mode 100644 index 000000000..911b9b8e9 --- /dev/null +++ b/crates/ppvm-vihaco/tests/branch_on_outcome.sst @@ -0,0 +1,29 @@ +device circuit.n_qubits 2; + +fn @main() { + const.u64 0 + circuit h + + const.u64 0 + circuit measure + + // Stack: [outcome]. No loss gate, so outcome is 0 or 1. Compare to 1 + // to derive a bool for cond_br. + const.u32 1 + eq.u32 + + cond_br @one, @zero + + @one: + const.u64 1 + circuit x + br @measure_q1 + + @zero: + br @measure_q1 + + @measure_q1: + const.u64 1 + circuit measure + ret +} diff --git a/crates/ppvm-vihaco/tests/branch_on_outcome_x.sst b/crates/ppvm-vihaco/tests/branch_on_outcome_x.sst new file mode 100644 index 000000000..f410cb8fb --- /dev/null +++ b/crates/ppvm-vihaco/tests/branch_on_outcome_x.sst @@ -0,0 +1,30 @@ +device circuit.n_qubits 2; + +fn @main() { + // X on q0 -> |1>, measure -> outcome is deterministically 1. + const.u64 0 + circuit x + + const.u64 0 + circuit measure + + // Stack: [outcome]. No loss gate, so outcome is 0 or 1. Compare to 1 + // to derive a bool for cond_br. + const.u32 1 + eq.u32 + + cond_br @one, @zero + + @one: + const.u64 1 + circuit x + br @measure_q1 + + @zero: + br @measure_q1 + + @measure_q1: + const.u64 1 + circuit measure + ret +} diff --git a/crates/ppvm-vihaco/tests/function_call.sst b/crates/ppvm-vihaco/tests/function_call.sst new file mode 100644 index 000000000..0761fd256 --- /dev/null +++ b/crates/ppvm-vihaco/tests/function_call.sst @@ -0,0 +1,19 @@ +device circuit.n_qubits 2; + +fn @main() { + // Jump into the helper, which finishes the program with `halt`. + // Using `halt` instead of `ret` from the callee avoids depending on + // vihaco-cpu restoring a return PC, which it doesn't track today. + call 0, @run_circuit + ret +} + +fn @run_circuit() { + const.u64 1 + circuit h + + const.u64 1 + circuit measure + + ret 1 +} diff --git a/crates/ppvm-vihaco/tests/function_call_branch_both.sst b/crates/ppvm-vihaco/tests/function_call_branch_both.sst new file mode 100644 index 000000000..1c10a6372 --- /dev/null +++ b/crates/ppvm-vihaco/tests/function_call_branch_both.sst @@ -0,0 +1,63 @@ +device circuit.n_qubits 2; + +// Exercises a helper that returns the tri-state measurement outcome via +// `ret 1`, with main branching on loss first, then on the 0/1 outcome. +// +// Layout after `call 0, @measure_q0` returns: stack = [outcome] where +// outcome ∈ {0, 1, 2} (2 = Lost). +// +// Loss prob is 0.5, so: +// P(lost) = 0.5 → q1 flipped to |1> → m1 = 1 +// P(kept ∧ outcome = 1) = 0.25 → q1 flipped to |1> → m1 = 1 +// P(kept ∧ outcome = 0) = 0.25 → q1 stays in |0> → m1 = 0 +// → P(m1 = 1) = 0.75. +fn @main() { + const.u64 0 + circuit h + + const.u64 0 + const.f64 0.5 + circuit loss + + call 0, @measure_q0 + + // Stack: [outcome]. Branch on Lost (outcome == 2) first. + dup + const.u32 2 + eq.u32 + cond_br @lost, @kept + + @lost: + // is_lost = true: outcome is meaningless. Flip q1 to mark "lost" + // path. The leftover `outcome` value on the stack is harmless because + // we halt at @final without reading it. + const.u64 1 + circuit x + br @final + + @kept: + // Stack: [outcome (0 or 1)]. Compare to 1 to derive a bool. + const.u32 1 + eq.u32 + cond_br @outcome_one, @outcome_zero + + @outcome_one: + const.u64 1 + circuit x + br @final + + @outcome_zero: + br @final + + @final: + const.u64 1 + circuit measure + halt +} + +fn @measure_q0() { + const.u64 0 + circuit measure + // Stack: [outcome] + ret 1 +} diff --git a/crates/ppvm-vihaco/tests/function_call_ret.sst b/crates/ppvm-vihaco/tests/function_call_ret.sst new file mode 100644 index 000000000..90d5907d0 --- /dev/null +++ b/crates/ppvm-vihaco/tests/function_call_ret.sst @@ -0,0 +1,42 @@ +device circuit.n_qubits 2; + +// TODO: aspirational — depends on `ret ` restoring the caller's PC and +// leaving the top `n` values on the caller's stack. The runtime doesn't do +// this today (Frame has no return_pc), so this fixture currently fails. + +fn @main() { + // Put q0 into |+>. + const.u64 0 + circuit h + + // Measure q1 via a helper that returns the outcome on top of the stack. + call 0, @measure_q1 + + // Stack: [outcome]. Compare to 1 to derive a bool for cond_br. + const.u32 1 + eq.u32 + cond_br @one, @zero + + @one: + // outcome was 1: apply X to q0 as a correction. + const.u64 0 + circuit x + br @done + + @zero: + br @done + + @done: + ret +} + +fn @measure_q1() -> u32 { + const.u64 1 + circuit h + + const.u64 1 + circuit measure + // Stack: [outcome] + + ret 1 +} diff --git a/crates/ppvm-vihaco/tests/hello_circuit.sst b/crates/ppvm-vihaco/tests/hello_circuit.sst new file mode 100644 index 000000000..6144ce02b --- /dev/null +++ b/crates/ppvm-vihaco/tests/hello_circuit.sst @@ -0,0 +1,16 @@ +device circuit.n_qubits 2; + +fn @main() { + const.u64 0 + circuit h + + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 0 + const.f64 0.1 + circuit rx + + ret +} diff --git a/crates/ppvm-vihaco/tests/lossy_paulisum_loss_trace.sst b/crates/ppvm-vihaco/tests/lossy_paulisum_loss_trace.sst new file mode 100644 index 000000000..36462b211 --- /dev/null +++ b/crates/ppvm-vihaco/tests/lossy_paulisum_loss_trace.sst @@ -0,0 +1,28 @@ +device circuit.n_qubits 2; +device circuit.backend lossy_paulisum; +device circuit.observable ZZ; + +// LossyPauliSum end-to-end with a single-qubit loss channel. The header +// seeds `ZZ`, the body Heisenberg-propagates ZZ backward through CNOT(0,1) +// to Z_1 (= IZ), then applies loss(q0, p=0.3). The pattern `Z?*` matches +// all Z/I-only Paulis on the surviving qubits, so the trace picks up the +// IZ coefficient that survived the loss channel. +// +// The Rust-side test cross-checks this against a pure-Rust LossyPauliSum +// reference with the same Config/strategy, so we don't have to commit to +// the exact loss-channel value — only that .sst and direct API agree. +fn @main() { + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 0 + const.f64 0.3 + circuit loss + + circuit truncate + + const.str "Z?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_bell_trace.sst b/crates/ppvm-vihaco/tests/paulisum_bell_trace.sst new file mode 100644 index 000000000..74dacb781 --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_bell_trace.sst @@ -0,0 +1,18 @@ +device circuit.n_qubits 2; +device circuit.backend paulisum; +device circuit.observable ZZ; + +fn @main() { + // Textbook H(0); CNOT(0,1) — emit reversed for Heisenberg propagation. + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 0 + circuit h + + // Trace against |00>: match Z-or-identity on every qubit. + const.str "Z?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_ghz_xxx_trace.sst b/crates/ppvm-vihaco/tests/paulisum_ghz_xxx_trace.sst new file mode 100644 index 000000000..8e5f31a1b --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_ghz_xxx_trace.sst @@ -0,0 +1,27 @@ +device circuit.n_qubits 3; +device circuit.backend paulisum; +device circuit.observable XXX; + +fn @main() { + // Heisenberg backward through GHZ prep: forward is H(0); CNOT(0,1); CNOT(1,2), + // so we apply CNOT(1,2); CNOT(0,1); H(0) here. + // + // XXX --CNOT(1,2)--> XXI (X_2 on target absorbs into X_1 on control) + // --CNOT(0,1)--> XII + // --H(0)------> ZII + // Trace against Z/I-only Paulis picks up the coefficient of ZII = 1.0. + const.u64 1 + const.u64 2 + circuit cnot + + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 0 + circuit h + + const.str "Z?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_measure_error.sst b/crates/ppvm-vihaco/tests/paulisum_measure_error.sst new file mode 100644 index 000000000..a65de9a2f --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_measure_error.sst @@ -0,0 +1,9 @@ +device circuit.n_qubits 1; +device circuit.backend paulisum; +device circuit.observable Z; + +fn @main() { + const.u64 0 + circuit measure + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_multi_term_trace.sst b/crates/ppvm-vihaco/tests/paulisum_multi_term_trace.sst new file mode 100644 index 000000000..48f91bcc2 --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_multi_term_trace.sst @@ -0,0 +1,11 @@ +device circuit.n_qubits 2; +device circuit.backend paulisum; +device circuit.observable 1.0*ZZ+0.5*XX; + +fn @main() { + // No gates — the multi-term observable seeds the state directly. + // Pattern `[XZ]?*` matches both ZZ (coef 1.0) and XX (coef 0.5). + const.str "[XZ]?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_ry_z_trace.sst b/crates/ppvm-vihaco/tests/paulisum_ry_z_trace.sst new file mode 100644 index 000000000..1f8a66212 --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_ry_z_trace.sst @@ -0,0 +1,17 @@ +device circuit.n_qubits 1; +device circuit.backend paulisum; +device circuit.observable Z; + +fn @main() { + // PauliSum already runs in the Heisenberg picture: `circuit ry` performs + // RY(θ)† Z RY(θ) = cos(θ)·Z + sin(θ)·X. A single gate means no + // reversal is needed beyond that. Trace against Z/I-only Paulis picks + // up the cos(θ) coefficient on Z; the sin(θ)·X term contributes 0. + const.u64 0 + const.f64 0.7 + circuit ry + + const.str "Z?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/paulisum_trotter_truncate.sst b/crates/ppvm-vihaco/tests/paulisum_trotter_truncate.sst new file mode 100644 index 000000000..ce50fb399 --- /dev/null +++ b/crates/ppvm-vihaco/tests/paulisum_trotter_truncate.sst @@ -0,0 +1,37 @@ +device circuit.n_qubits 2; +device circuit.backend paulisum; +device circuit.observable ZZ; +device circuit.coefficient_threshold 1e-6; + +fn @main() { + // Two Trotter layers of RXX(0.1) RZZ(0.05), with explicit truncate + // between layers. RXX branches Z->Y so the sum grows; truncate prunes + // small-coefficient terms via `coefficient_threshold`. + const.u64 0 + const.u64 1 + const.f64 0.1 + circuit rxx + + const.u64 0 + const.u64 1 + const.f64 0.05 + circuit rzz + + circuit truncate + + const.u64 0 + const.u64 1 + const.f64 0.1 + circuit rxx + + const.u64 0 + const.u64 1 + const.f64 0.05 + circuit rzz + + circuit truncate + + const.str "Z?*" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/rotxy.sst b/crates/ppvm-vihaco/tests/rotxy.sst new file mode 100644 index 000000000..39bf045be --- /dev/null +++ b/crates/ppvm-vihaco/tests/rotxy.sst @@ -0,0 +1,15 @@ +device circuit.n_qubits 1; + +fn @main() { + // R(axis_angle = π/2, θ = π) == RY(π), so |0> is sent to |1>. + // Stack order for `circuit r`: qubit, then axis_angle, then theta. + const.u64 0 + const.f64 1.5707963267948966 + const.f64 3.141592653589793 + circuit r + + const.u64 0 + circuit measure + + ret +} diff --git a/crates/ppvm-vihaco/tests/sst_fixtures.rs b/crates/ppvm-vihaco/tests/sst_fixtures.rs new file mode 100644 index 000000000..8c4fb9839 --- /dev/null +++ b/crates/ppvm-vihaco/tests/sst_fixtures.rs @@ -0,0 +1,532 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +//! End-to-end fixture coverage: parse + resolve + run each `.sst` file in +//! this directory via the public `PPVM` API. + +use ppvm_vihaco::composite::PPVM; +use ppvm_vihaco::measurements::MeasurementOutcome; + +/// Dump a fixture to a `.ssb` file, load it back, and run it. Exercises the +/// full bytecode round-trip through disk: `dump_file` → `load_bytecode_file`. +fn dump_load_run(sst_path: &str, ssb_name: &str) -> PPVM { + let out = std::env::temp_dir().join(ssb_name); + let out = out.to_str().expect("utf-8 temp path"); + ppvm_vihaco::dump_file(sst_path, out).unwrap_or_else(|e| panic!("dump {sst_path}: {e:?}")); + + let mut machine = PPVM::default(); + machine + .load_bytecode_file(out) + .unwrap_or_else(|e| panic!("load {out}: {e:?}")); + machine.run().unwrap_or_else(|e| panic!("run {out}: {e:?}")); + + let _ = std::fs::remove_file(out); + machine +} + +#[test] +fn bell_sst_runs_and_records_two_measurements() { + let mut machine = PPVM::default(); + machine + .run_file("tests/bell.sst") + .unwrap_or_else(|e| panic!("run bell.sst: {e:?}")); + assert_eq!(machine.measurement_record().len(), 2); +} + +#[test] +fn hello_circuit_sst_parses_and_runs() { + let mut machine = PPVM::default(); + machine + .run_file("tests/hello_circuit.sst") + .unwrap_or_else(|e| panic!("run hello_circuit.sst: {e:?}")); + // hello_circuit.sst applies H + CNOT + RX(0.1); no measurements. + assert_eq!(machine.measurement_record().len(), 0); +} + +#[test] +fn rotxy_sst_runs_and_flips_qubit() { + // `rotxy.sst` applies R(axis_angle=π/2, θ=π) = RY(π) to q0, deterministically + // sending |0> → |1>, then measures it. Exercises the `circuit r` path end to + // end: parse → resolve (pop θ, axis_angle, qubit) → execute via `tab.r`. + let machine = + ppvm_vihaco::run_file("tests/rotxy.sst").unwrap_or_else(|e| panic!("run rotxy.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1, "expected exactly one measurement"); + assert_eq!( + record[0].as_slice(), + &[MeasurementOutcome::One], + "R(π/2, π) = RY(π) must flip q0 to 1" + ); +} + +#[test] +fn dumped_rotxy_runs_and_flips_qubit() { + // Same program, but through the bytecode round-trip: confirms the `R` + // instruction survives dump → `.ssb` → load → execute. + let machine = dump_load_run("tests/rotxy.sst", "ppvm_dump_rotxy.ssb"); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1); + assert_eq!(record[0].as_slice(), &[MeasurementOutcome::One]); +} + +#[test] +fn run_file_via_library_helper() { + let machine = + ppvm_vihaco::run_file("tests/bell.sst").unwrap_or_else(|e| panic!("run bell.sst: {e:?}")); + assert_eq!(machine.measurement_record().len(), 2); +} + +#[test] +fn function_call_jumps_into_callee_body() { + // `function_call.sst` has main `call` into `@run_circuit`, which puts q1 + // in |+>, measures it, and `halt`s. Verifies CallPatch resolves the + // symbolic target and op_call actually transfers control there. + let machine = ppvm_vihaco::run_file("tests/function_call.sst") + .unwrap_or_else(|e| panic!("run function_call.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1, "expected exactly one measurement"); + assert_eq!(record[0].len(), 1); + assert!( + record[0][0] != MeasurementOutcome::Lost, + "measurement should not be lost" + ); +} + +#[test] +fn function_call_returns() { + let machine = ppvm_vihaco::run_file("tests/function_call_ret.sst") + .unwrap_or_else(|e| panic!("run function_call.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1, "expected exactly one measurement"); + assert_eq!(record[0].len(), 1); + assert!( + record[0][0] != MeasurementOutcome::Lost, + "measurement should not be lost" + ); +} + +#[test] +fn branch_on_outcome_deterministic_x_path() { + // `branch_on_outcome_x.sst` applies X to q0 instead of H, so the outcome + // is deterministically 1. The cond_br must therefore take the @one path, + // which flips q1 before measuring it, yielding m1 = 1 as well. + let machine = ppvm_vihaco::run_file("tests/branch_on_outcome_x.sst") + .unwrap_or_else(|e| panic!("run branch_on_outcome_x.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2, "expected exactly two measurements"); + assert_eq!( + record[0].as_slice(), + &[MeasurementOutcome::One], + "X-prepared q0 must measure 1" + ); + assert_eq!( + record[1].as_slice(), + &[MeasurementOutcome::One], + "branch must have flipped q1" + ); +} + +#[test] +fn branch_on_outcome_statistics_balanced_and_invariant_holds() { + // `branch_on_outcome.sst` puts q0 in |+>, so its measurement is a fair + // coin. The branch then flips q1 iff the outcome was 1, making m1 == m0 + // an invariant on every shot. Run many shots and check both properties. + const SHOTS: usize = 400; + let mut ones = 0usize; + for _ in 0..SHOTS { + let machine = ppvm_vihaco::run_file("tests/branch_on_outcome.sst") + .unwrap_or_else(|e| panic!("run branch_on_outcome.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2); + let m0 = record[0][0]; + let m1 = record[1][0]; + assert_eq!(m0, m1, "branch must steer q1 to match q0 on every shot"); + assert!( + m0 != MeasurementOutcome::Lost, + "measurement should not be lost" + ); + if m0 == MeasurementOutcome::One { + ones += 1; + } + } + // Fair coin with SHOTS=400: mean=200, stddev=10. ±6σ window catches a + // truly broken RNG without flaking on a healthy one. + let lo = SHOTS / 2 - 60; + let hi = SHOTS / 2 + 60; + assert!( + (lo..=hi).contains(&ones), + "expected {lo}..={hi} ones out of {SHOTS}, got {ones}" + ); +} + +#[test] +fn function_call_branch_on_both_returned_values() { + // `function_call_branch_both.sst`: helper returns the tri-state outcome + // (0/1/Lost) via `ret 1`. Main first branches on is_lost, then on the + // 0/1 outcome, steering q1 to |1> on the lost path and on the + // kept-outcome=1 path, leaving q1 in |0> only on the kept-outcome=0 + // path. With loss prob 0.5 and a |+> prep: + // P(m1 = 1) = P(lost) + P(kept) · P(outcome = 1 | kept) + // = 0.5 + 0.5 · 0.5 = 0.75 + // P(m0 = lost) = 0.5 + const SHOTS: usize = 400; + let mut q0_lost = 0usize; + let mut q1_ones = 0usize; + for _ in 0..SHOTS { + let machine = ppvm_vihaco::run_file("tests/function_call_branch_both.sst") + .unwrap_or_else(|e| panic!("run function_call_branch_both.sst: {e:?}")); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2, "expected exactly two measurements"); + assert_eq!(record[0].len(), 1); + assert_eq!(record[1].len(), 1); + if record[0][0] == MeasurementOutcome::Lost { + q0_lost += 1; + } + if record[1][0] == MeasurementOutcome::One { + q1_ones += 1; + } + } + // P(lost) = 0.5, SHOTS=400: mean=200, stddev=10. ±6σ window. + assert!( + (140..=260).contains(&q0_lost), + "expected ~200 lost shots, got {q0_lost}" + ); + // P(m1=1) = 0.75, SHOTS=400: mean=300, stddev≈8.66. ±6σ → ~248..352. + assert!( + (240..=360).contains(&q1_ones), + "expected ~300 q1=true shots, got {q1_ones}" + ); +} + +// ─── Task 12: PauliSum / LossyPauliSum end-to-end via .sst source ──────── + +#[test] +fn paulisum_bell_zz_trace_through_sst() { + // Bell-state ⟨ZZ⟩ via PauliSum. Textbook circuit H(0); CNOT(0,1) is + // emitted reversed for Heisenberg propagation: `circuit cnot; circuit h`. + // Conjugating ZZ by CNOT(0,1) gives Z_1 (= IZ); H on q0 leaves IZ + // untouched. Tracing against |00> matches IZ (pattern `Z?*`) and + // returns +1.0 — matching ⟨Φ+|ZZ|Φ+⟩ = 1. + let machine = ppvm_vihaco::run_file("tests/paulisum_bell_trace.sst") + .unwrap_or_else(|e| panic!("run paulisum_bell_trace.sst: {e:?}")); + let trace = machine.trace_record(); + assert_eq!(trace.len(), 1, "expected one trace emission"); + assert!( + (trace[0] - 1.0).abs() < 1e-12, + "expected ⟨ZZ⟩ = 1.0, got {}", + trace[0] + ); +} + +#[test] +fn paulisum_multi_term_observable_trace_through_sst() { + // Phase 3 wiring: a 2-term observable `1.0*ZZ+0.5*XX` parses on the + // header, seeds both terms, and an end-of-program trace picks up their + // contributions. With no gates applied, tracing `[XZ]?*` matches both + // ZZ and XX directly → coef sum = 1.0 + 0.5 = 1.5. + let machine = ppvm_vihaco::run_file("tests/paulisum_multi_term_trace.sst") + .unwrap_or_else(|e| panic!("run paulisum_multi_term_trace.sst: {e:?}")); + let trace = machine.trace_record(); + assert_eq!(trace.len(), 1); + assert!( + (trace[0] - 1.5).abs() < 1e-12, + "expected 1.5, got {}", + trace[0] + ); +} + +#[test] +fn paulisum_trotter_matches_pure_rust_reference() { + // Two Trotter layers of RXX(0.1) + RZZ(0.05), interleaved with explicit + // `circuit truncate`. The .sst-driven path should agree bit-for-bit with a + // pure Rust PauliSum running the same gates: `indexmap::ByteFxHashF64` + // gives deterministic iteration order (Decision 7), so truncation order + // and float accumulation are stable across both paths. + + let machine = ppvm_vihaco::run_file("tests/paulisum_trotter_truncate.sst") + .unwrap_or_else(|e| panic!("run paulisum_trotter_truncate.sst: {e:?}")); + let through_sst = machine.trace_record(); + assert_eq!(through_sst.len(), 1, "expected one trace emission"); + + // Pure Rust reference: same N=8 / strategy / circuit order as the PauliSum + // Bits64 bucket in `ppvm_vihaco::component`. + use ppvm_pauli_sum::config::indexmap::ByteFxHashF64; + use ppvm_pauli_sum::prelude::*; + use ppvm_pauli_sum::strategy::{CoefficientThreshold, CombinedStrategy, MaxPauliWeight}; + type RefConfig = ByteFxHashF64<8, CombinedStrategy>; + + let mut state: PauliSum = PauliSum::builder() + .n_qubits(2) + .strategy(CombinedStrategy( + CoefficientThreshold(1e-6), + MaxPauliWeight(usize::MAX), + )) + .build(); + state += ("ZZ", 1.0); + state.rxx(0, 1, 0.1); + state.rzz(0, 1, 0.05); + state.truncate(); + state.rxx(0, 1, 0.1); + state.rzz(0, 1, 0.05); + state.truncate(); + let pat = PauliPattern::parse("Z?*").expect("parse pattern"); + let reference = state.trace(&pat); + + assert_eq!( + through_sst[0], reference, + ".sst-driven trace must match pure Rust reference bit-for-bit" + ); +} + +#[test] +fn lossy_paulisum_loss_trace_matches_pure_rust_reference() { + // End-to-end LossyPauliSum: header seeds ZZ, body applies CNOT(0,1) + // then loss(q0, 0.3), then traces. The .sst-driven trace must agree + // bit-for-bit with a pure-Rust reference using the same Config (a + // `LossyPauliSumConfig<8>` matching the Bits64 bucket) and operations. + let machine = ppvm_vihaco::run_file("tests/lossy_paulisum_loss_trace.sst") + .unwrap_or_else(|e| panic!("run lossy_paulisum_loss_trace.sst: {e:?}")); + let through_sst = machine.trace_record(); + assert_eq!(through_sst.len(), 1, "expected one trace emission"); + + use ppvm_pauli_sum::config::indexmap::ByteFxHashF64; + use ppvm_pauli_sum::prelude::*; + use ppvm_pauli_sum::strategy::{CoefficientThreshold, CombinedStrategy, MaxPauliWeight}; + type RefConfig = ByteFxHashF64< + 8, + CombinedStrategy, + LossyPauliWord<[u8; 8]>, + >; + + let mut state: PauliSum = PauliSum::builder() + .n_qubits(2) + .strategy(CombinedStrategy( + CoefficientThreshold(1e-10), + MaxPauliWeight(usize::MAX), + )) + .build(); + state += ("ZZ", 1.0); + state.cnot(0, 1); + state.loss_channel(0, 0.3); + state.truncate(); + let pat = PauliPattern::parse("Z?*").expect("parse pattern"); + let reference = state.trace(&pat); + + assert_eq!( + through_sst[0], reference, + ".sst-driven trace must match pure Rust reference bit-for-bit" + ); +} + +#[test] +fn paulisum_measure_returns_unsupported_error() { + // Per Decision 11, Measure on PauliSum hits the dispatch fallback with a + // clear "not supported on the PauliSum backend" error. + let mut machine = PPVM::default(); + machine + .load_file("tests/paulisum_measure_error.sst") + .unwrap_or_else(|e| panic!("load paulisum_measure_error.sst: {e:?}")); + let err = machine.run().unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("not supported on the PauliSum backend"), + "expected PauliSum-rejection error, got: {msg}" + ); +} + +// ─── Task 16: Tableau-side Trace, cross-backend agreement ──────────────── + +#[test] +fn tableau_bell_zz_trace_through_sst() { + // Bell-state ⟨ZZ⟩ via the Tableau backend's `Trace` instruction: + // forward H(0); CNOT(0, 1) leaves |ψ⟩ = |Φ+⟩, and `Z0Z1` matches exactly + // the Pauli word ZZ, so `tab.trace(&pat) = ⟨Φ+|ZZ|Φ+⟩ = 1`. + let machine = ppvm_vihaco::run_file("tests/tableau_bell_trace.sst") + .unwrap_or_else(|e| panic!("run tableau_bell_trace.sst: {e:?}")); + let trace = machine.trace_record(); + assert_eq!(trace.len(), 1, "expected one trace emission"); + assert!( + (trace[0] - 1.0).abs() < 1e-12, + "expected ⟨ZZ⟩ = 1.0, got {}", + trace[0] + ); +} + +fn assert_cross_backend_agreement(tableau_sst: &str, paulisum_sst: &str) { + let tab = + ppvm_vihaco::run_file(tableau_sst).unwrap_or_else(|e| panic!("run {tableau_sst}: {e:?}")); + let ps = + ppvm_vihaco::run_file(paulisum_sst).unwrap_or_else(|e| panic!("run {paulisum_sst}: {e:?}")); + let tab_v = tab.trace_record(); + let ps_v = ps.trace_record(); + assert_eq!(tab_v.len(), 1, "{tableau_sst}: expected one trace emission"); + assert_eq!(ps_v.len(), 1, "{paulisum_sst}: expected one trace emission"); + assert!( + (tab_v[0] - ps_v[0]).abs() < 1e-12, + "tableau {} vs PauliSum {} (|Δ|={}) for {tableau_sst} ↔ {paulisum_sst}", + tab_v[0], + ps_v[0], + (tab_v[0] - ps_v[0]).abs() + ); +} + +#[test] +fn tableau_and_paulisum_agree_on_bell_zz_trace() { + // ⟨Φ+|ZZ|Φ+⟩ = 1. Tableau forward-evolves |0…0⟩ and matches `Z0Z1`; + // PauliSum Heisenberg-propagates ZZ backward through the reversed + // circuit, then sums Z/I-only coefficients. + assert_cross_backend_agreement( + "tests/tableau_bell_trace.sst", + "tests/paulisum_bell_trace.sst", + ); +} + +#[test] +fn tableau_and_paulisum_agree_on_ghz_xxx_trace() { + // ⟨GHZ|XXX|GHZ⟩ = 1. Exercises a 3-qubit Clifford chain (H + two + // CNOTs) and the non-trivial Heisenberg evolution XXX → ZII through + // the reversed circuit on the PauliSum side. + assert_cross_backend_agreement( + "tests/tableau_ghz_xxx_trace.sst", + "tests/paulisum_ghz_xxx_trace.sst", + ); +} + +#[test] +fn tableau_and_paulisum_agree_on_ry_z_trace() { + // ⟨RY(θ)|0⟩|Z|RY(θ)|0⟩⟩ = cos(θ). Exercises a non-Clifford rotation: + // the tableau opens a branch via `tab.ry`, while the PauliSum applies + // the Heisenberg dual RY(θ)†·Z·RY(θ) = cos(θ)·Z + sin(θ)·X in one step. + assert_cross_backend_agreement( + "tests/tableau_ry_z_trace.sst", + "tests/paulisum_ry_z_trace.sst", + ); +} + +// ─── Auto-detect via load_file: route by content, not extension ─────────── + +#[test] +fn is_bytecode_distinguishes_ssb_from_sst() { + let sst = std::fs::read("tests/bell.sst").expect("read bell.sst"); + assert!( + !ppvm_vihaco::bytecode::is_bytecode(&sst), + ".sst source must not be detected as bytecode" + ); + + let ssb = ppvm_vihaco::bytecode::compile_to_bytes( + &String::from_utf8(sst).expect("bell.sst is utf-8"), + ) + .expect("compile bell.sst"); + assert!( + ppvm_vihaco::bytecode::is_bytecode(&ssb), + "dumped .ssb must be detected as bytecode" + ); + + // Inputs shorter than the 4-byte magic are never bytecode. Note "PPVM" as + // text also fails: the magic is a little-endian u32, so its on-disk bytes + // are "MVPP", not "PPVM". + assert!(!ppvm_vihaco::bytecode::is_bytecode(b"PPV")); + assert!(!ppvm_vihaco::bytecode::is_bytecode(b"")); + assert!(!ppvm_vihaco::bytecode::is_bytecode(b"PPVM")); +} + +#[test] +fn load_file_auto_detects_bytecode_and_text() { + // Use the deterministic X-prepared fixture: q0 measures 1, the branch + // flips q1, so both routes must yield exactly [1], [1]. Any divergence — + // or a binary file mis-parsed as text — fails loudly. + let from_text = ppvm_vihaco::run_file("tests/branch_on_outcome_x.sst") + .unwrap_or_else(|e| panic!("run .sst via load_file: {e:?}")); + + // Dump the same fixture to a `.ssb` and run *that file* through the same + // run_file entry point. If load_file didn't sniff the magic it would try + // to parse the binary as text and error. + let out = std::env::temp_dir().join("ppvm_autodetect_branch_x.ssb"); + let out = out.to_str().expect("utf-8 temp path"); + ppvm_vihaco::dump_file("tests/branch_on_outcome_x.sst", out) + .unwrap_or_else(|e| panic!("dump: {e:?}")); + let from_binary = ppvm_vihaco::run_file(out).unwrap_or_else(|e| panic!("run .ssb: {e:?}")); + let _ = std::fs::remove_file(out); + + for (label, machine) in [("text", &from_text), ("binary", &from_binary)] { + let record = machine.measurement_record(); + assert_eq!(record.len(), 2, "{label}: expected two measurements"); + assert_eq!( + record[0].as_slice(), + &[MeasurementOutcome::One], + "{label}: X-prepared q0 must measure 1" + ); + assert_eq!( + record[1].as_slice(), + &[MeasurementOutcome::One], + "{label}: branch must flip q1" + ); + } +} + +// ─── Bytecode round-trip: dump → load → execute each fixture ────────────── + +#[test] +fn dumped_bell_records_two_measurements() { + let machine = dump_load_run("tests/bell.sst", "ppvm_dump_bell.ssb"); + assert_eq!(machine.measurement_record().len(), 2); +} + +#[test] +fn dumped_hello_circuit_runs_with_no_measurements() { + let machine = dump_load_run("tests/hello_circuit.sst", "ppvm_dump_hello_circuit.ssb"); + assert_eq!(machine.measurement_record().len(), 0); +} + +#[test] +fn dumped_function_call_executes_callee() { + let machine = dump_load_run("tests/function_call.sst", "ppvm_dump_function_call.ssb"); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1); + assert_eq!(record[0].len(), 1); + assert!(record[0][0] != MeasurementOutcome::Lost); +} + +#[test] +fn dumped_function_call_ret_executes() { + let machine = dump_load_run( + "tests/function_call_ret.sst", + "ppvm_dump_function_call_ret.ssb", + ); + let record = machine.measurement_record(); + assert_eq!(record.len(), 1); + assert_eq!(record[0].len(), 1); +} + +#[test] +fn dumped_branch_on_outcome_x_is_deterministic() { + // X-prepared q0 measures 1, so the branch flips q1 → both outcomes are 1. + // Confirms branch targets survive the dump/load round-trip. + let machine = dump_load_run("tests/branch_on_outcome_x.sst", "ppvm_dump_branch_x.ssb"); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2); + assert_eq!(record[0].as_slice(), &[MeasurementOutcome::One]); + assert_eq!(record[1].as_slice(), &[MeasurementOutcome::One]); +} + +#[test] +fn dumped_branch_on_outcome_preserves_invariant() { + // q0 in |+> is a fair coin, but the branch steers q1 to match q0 every + // shot — that invariant must hold after a round-trip. + let machine = dump_load_run("tests/branch_on_outcome.sst", "ppvm_dump_branch.ssb"); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2); + assert_eq!(record[0][0], record[1][0]); +} + +#[test] +fn dumped_function_call_branch_both_runs() { + let machine = dump_load_run( + "tests/function_call_branch_both.sst", + "ppvm_dump_function_call_branch_both.ssb", + ); + let record = machine.measurement_record(); + assert_eq!(record.len(), 2); + assert_eq!(record[0].len(), 1); + assert_eq!(record[1].len(), 1); +} diff --git a/crates/ppvm-vihaco/tests/tableau_bell_trace.sst b/crates/ppvm-vihaco/tests/tableau_bell_trace.sst new file mode 100644 index 000000000..a5e0a94c4 --- /dev/null +++ b/crates/ppvm-vihaco/tests/tableau_bell_trace.sst @@ -0,0 +1,18 @@ +device circuit.n_qubits 2; + +fn @main() { + // Forward Bell prep: H(0); CNOT(0, 1) → |Φ+⟩. + const.u64 0 + circuit h + + const.u64 0 + const.u64 1 + circuit cnot + + // Tableau-side trace: Σ_{P matches pat} ⟨ψ|P|ψ⟩. Positional `Z0Z1` matches + // exactly the ZZ word, so this returns ⟨Φ+|ZZ|Φ+⟩ = 1.0 — the same value + // the PauliSum backend produces by Heisenberg-propagating ZZ backward. + const.str "Z0Z1" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/tableau_ghz_xxx_trace.sst b/crates/ppvm-vihaco/tests/tableau_ghz_xxx_trace.sst new file mode 100644 index 000000000..5a7c58989 --- /dev/null +++ b/crates/ppvm-vihaco/tests/tableau_ghz_xxx_trace.sst @@ -0,0 +1,20 @@ +device circuit.n_qubits 3; + +fn @main() { + // Forward GHZ prep: H(0); CNOT(0, 1); CNOT(1, 2) → (|000⟩+|111⟩)/√2. + const.u64 0 + circuit h + + const.u64 0 + const.u64 1 + circuit cnot + + const.u64 1 + const.u64 2 + circuit cnot + + // `X{3}` matches exactly the Pauli word XXX. ⟨GHZ|XXX|GHZ⟩ = 1. + const.str "X{3}" + circuit trace + ret +} diff --git a/crates/ppvm-vihaco/tests/tableau_ry_z_trace.sst b/crates/ppvm-vihaco/tests/tableau_ry_z_trace.sst new file mode 100644 index 000000000..45c78d006 --- /dev/null +++ b/crates/ppvm-vihaco/tests/tableau_ry_z_trace.sst @@ -0,0 +1,13 @@ +device circuit.n_qubits 1; + +fn @main() { + // RY(θ)|0⟩ = cos(θ/2)|0⟩ + sin(θ/2)|1⟩, so ⟨ψ|Z|ψ⟩ = cos(θ). + // Hard-coded θ = 0.7 → cos(0.7) ≈ 0.7648421872844885. + const.u64 0 + const.f64 0.7 + circuit ry + + const.str "Z{1}" + circuit trace + ret +} diff --git a/crates/vihaco-circuit-isa/Cargo.toml b/crates/vihaco-circuit-isa/Cargo.toml new file mode 100644 index 000000000..344841021 --- /dev/null +++ b/crates/vihaco-circuit-isa/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "vihaco-circuit-isa" +version = "0.1.0" +edition = "2024" + +[dependencies] +chumsky = "0.10" +eyre = "0.6.12" +smallvec = "1.15.1" +vihaco = "0.1.1" +vihaco-parser = "0.1.1" +vihaco-parser-core = "0.1.1" diff --git a/crates/vihaco-circuit-isa/src/lib.rs b/crates/vihaco-circuit-isa/src/lib.rs new file mode 100644 index 000000000..aab6b0b31 --- /dev/null +++ b/crates/vihaco-circuit-isa/src/lib.rs @@ -0,0 +1,336 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use smallvec::SmallVec; +use vihaco::Instruction; +use vihaco::Message; +use vihaco_parser::Parse; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Instruction, Parse)] +pub enum CircuitInstruction { + // NOTE: longer tokens need to go first + TwoQubitPauliError, // needs to go before T + Truncate, // needs to go before T + Trace, // needs to go before T + + // Single-Qubit Clifford gates + X, + Y, + Z, + H, + + #[token = "sqrt_x_adj"] + SqrtXAdj, + + #[token = "sqrt_x"] + SqrtX, + + #[token = "sqrt_y_adj"] + SqrtYAdj, + + #[token = "sqrt_y"] + SqrtY, + + #[token = "s_adj"] + SAdj, + S, + + // Controlled gates + CNOT, + CZ, + + // T gate + TAdj, + T, + + // Two-qubit rotations + RXX, + RYY, + RZZ, + + // Single-qubit rotations + RX, + RY, + RZ, + + // U3 + U3, + + // Measurement & Reset + Measure, + Reset, + + // RXY + R, + + // Loss + Loss, + CorrelatedLoss, + + // Noise + PauliError, + Depolarize2, + Depolarize, +} + +impl std::fmt::Display for CircuitInstruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use CircuitInstruction::*; + match self { + TwoQubitPauliError => write!(f, "TwoQubitPauliError"), + Truncate => write!(f, "Truncate"), + Trace => write!(f, "Trace"), + + X => write!(f, "X"), + Y => write!(f, "Y"), + Z => write!(f, "Z"), + H => write!(f, "H"), + + SqrtXAdj => write!(f, "SqrtXAdj"), + + SqrtX => write!(f, "SqrtX"), + + SqrtYAdj => write!(f, "SqrtYAdj"), + + SqrtY => write!(f, "SqrtY"), + + SAdj => write!(f, "SAdj"), + S => write!(f, "S"), + + CNOT => write!(f, "CNOT"), + CZ => write!(f, "CZ"), + + TAdj => write!(f, "TAdj"), + T => write!(f, "T"), + + RXX => write!(f, "RXX"), + RYY => write!(f, "RYY"), + RZZ => write!(f, "RZZ"), + + RX => write!(f, "RX"), + RY => write!(f, "RY"), + RZ => write!(f, "RZ"), + + U3 => write!(f, "U3"), + + Measure => write!(f, "Measure"), + Reset => write!(f, "Reset"), + + R => write!(f, "R"), + + Loss => write!(f, "Loss"), + CorrelatedLoss => write!(f, "CorrelatedLoss"), + + PauliError => write!(f, "PauliError"), + Depolarize2 => write!(f, "Depolarize2"), + Depolarize => write!(f, "Depolarize"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Message)] +pub enum CircuitMessage { + None, // Truncate (no operand) + Qubit(usize), // X, Y, Z, ... + QubitAndFloat(usize, f64), // RX, depolarize, ... + QubitAndTwoFloats(usize, f64, f64), // R + TwoQubit(usize, usize), // CX, CZ + TwoQubitAndFloat(usize, usize, f64), // RXX, ... + QubitU3(usize, f64, f64, f64), // U3 + QubitAndFloatArr3(usize, [f64; 3]), // PauliError + TwoQubitAndFloatArr3(usize, usize, [f64; 3]), // Correlated loss + TwoQubitAndFloatArr15(usize, usize, [f64; 15]), // TwoQubitPauliError + + PauliPatternStr(String), // Trace (resolved Pauli-pattern source) + + // batched instructions + QubitBatch(SmallVec<[usize; 8]>), // X, Y, Z, ... + QubitBatchAndFloat(SmallVec<[usize; 8]>, f64), // RX, depolarize, ... + TwoQubitBatch(SmallVec<[(usize, usize); 8]>), // CX, CZ + TwoQubitBatchAndFloat(SmallVec<[(usize, usize); 8]>, f64), // RXX, ... + QubitBatchU3(SmallVec<[usize; 8]>, f64, f64, f64), // U3 + QubitBatchAndFloatArr3(SmallVec<[usize; 8]>, [f64; 3]), // PauliError + TwoQubitBatchAndFloatArr3(SmallVec<[(usize, usize); 8]>, [f64; 3]), // Correlated loss + TwoQubitBatchAndFloatArr15(SmallVec<[(usize, usize); 8]>, [f64; 15]), // TwoQubitPauliError +} + +#[derive(Debug, Clone)] +pub struct CircuitEffect { + pub inst: CircuitInstruction, + pub msg: CircuitMessage, +} + +#[cfg(test)] +mod tests { + use super::CircuitInstruction::*; + use super::*; + + use chumsky::Parser as _; + use vihaco::instruction::{FromBytes, OpCode, WriteBytes}; + use vihaco_parser_core::Parse as _; + + /// Every variant, in declaration order. Anything iterating over the full + /// instruction set (round-trips, opcode uniqueness) goes through this so a + /// newly added variant is automatically covered. + const ALL: &[CircuitInstruction] = &[ + TwoQubitPauliError, + Truncate, + Trace, + X, + Y, + Z, + H, + SqrtXAdj, + SqrtX, + SqrtYAdj, + SqrtY, + SAdj, + S, + CNOT, + CZ, + TAdj, + T, + RXX, + RYY, + RZZ, + RX, + RY, + RZ, + U3, + Measure, + Reset, + R, + Loss, + CorrelatedLoss, + PauliError, + Depolarize2, + Depolarize, + ]; + + fn parse(src: &str) -> CircuitInstruction { + CircuitInstruction::parser() + .parse(src) + .into_result() + .unwrap_or_else(|e| panic!("parse of `{src}` failed: {e:?}")) + } + + // ─── Parse: tokens are the lowercased variant name ──────────────────── + + #[test] + fn parses_simple_lowercase_tokens() { + assert_eq!(parse("x"), X); + assert_eq!(parse("y"), Y); + assert_eq!(parse("z"), Z); + assert_eq!(parse("h"), H); + assert_eq!(parse("cnot"), CNOT); + assert_eq!(parse("cz"), CZ); + assert_eq!(parse("u3"), U3); + assert_eq!(parse("measure"), Measure); + assert_eq!(parse("reset"), Reset); + assert_eq!(parse("rxx"), RXX); + assert_eq!(parse("depolarize2"), Depolarize2); + assert_eq!(parse("depolarize"), Depolarize); + } + + // ─── Parse: prefix-sensitive disambiguation ─────────────────────────── + // + // These pairs share a prefix, so the declaration order in the enum is + // load-bearing: the longer token must win. These tests pin that contract. + + #[test] + fn parses_t_family_without_prefix_collision() { + // `t` is a prefix of `tadj`, `trace`, `truncate`, and `twoqubitpaulierror`. + assert_eq!(parse("t"), T); + assert_eq!(parse("tadj"), TAdj); + assert_eq!(parse("trace"), Trace); + assert_eq!(parse("truncate"), Truncate); + assert_eq!(parse("twoqubitpaulierror"), TwoQubitPauliError); + } + + #[test] + fn parses_s_family_without_prefix_collision() { + // `s` is a prefix of `s_adj`, `sqrt_x`, `sqrt_y`, etc. + assert_eq!(parse("s"), S); + assert_eq!(parse("s_adj"), SAdj); + assert_eq!(parse("sqrt_x"), SqrtX); + assert_eq!(parse("sqrt_x_adj"), SqrtXAdj); + assert_eq!(parse("sqrt_y"), SqrtY); + assert_eq!(parse("sqrt_y_adj"), SqrtYAdj); + } + + #[test] + fn rejects_unknown_token() { + assert!(CircuitInstruction::parser().parse("nope").has_errors()); + } + + #[test] + fn rejects_pascal_case_token() { + // The parse token is lowercase; the Display form must not parse back. + assert!(CircuitInstruction::parser().parse("CNOT").has_errors()); + } + + // ─── Display: PascalCase variant names ──────────────────────────────── + + #[test] + fn display_uses_pascal_case_names() { + assert_eq!(H.to_string(), "H"); + assert_eq!(CNOT.to_string(), "CNOT"); + assert_eq!(TwoQubitPauliError.to_string(), "TwoQubitPauliError"); + assert_eq!(Trace.to_string(), "Trace"); + assert_eq!(Truncate.to_string(), "Truncate"); + // Custom-token variants display their Rust name, not the parse token. + assert_eq!(SqrtXAdj.to_string(), "SqrtXAdj"); + assert_eq!(SAdj.to_string(), "SAdj"); + } + + // ─── Instruction codec (derived OpCode / WriteBytes / FromBytes) ────── + + #[test] + fn opcodes_are_unit_width() { + // All variants are field-less, so each encodes to a single byte. + assert_eq!(CircuitInstruction::width(), 1); + } + + #[test] + fn opcodes_are_unique() { + let mut seen = std::collections::HashSet::new(); + for inst in ALL { + assert!( + seen.insert(inst.opcode()), + "duplicate opcode {} for {inst:?}", + inst.opcode() + ); + } + assert_eq!(seen.len(), ALL.len()); + } + + #[test] + fn opcodes_match_declaration_order() { + // Opcodes default to the variant index, which is the on-disk contract + // for bytecode. Reordering variants silently breaks old bytecode, so + // pin the assignment here. + for (index, inst) in ALL.iter().enumerate() { + assert_eq!(inst.opcode() as usize, index, "{inst:?}"); + } + } + + #[test] + fn write_then_read_round_trips_every_variant() { + for inst in ALL { + let mut buf = Vec::new(); + inst.write_bytes(&mut buf).unwrap(); + assert_eq!(buf, [inst.opcode()], "{inst:?} should encode to one byte"); + + let mut cursor = std::io::Cursor::new(buf); + let back = CircuitInstruction::from_bytes(&mut cursor).unwrap(); + assert_eq!(back, *inst); + } + } + + #[test] + fn from_bytes_rejects_unknown_opcode() { + let mut cursor = std::io::Cursor::new([0xFFu8]); + let err = CircuitInstruction::from_bytes(&mut cursor).unwrap_err(); + assert!(err.to_string().contains("invalid opcode"), "err: {err}"); + } +} diff --git a/ppvm-python/src/ppvm/mixins.py b/ppvm-python/src/ppvm/mixins.py index dfa107b22..f26627015 100644 --- a/ppvm-python/src/ppvm/mixins.py +++ b/ppvm-python/src/ppvm/mixins.py @@ -221,6 +221,22 @@ def rz(self, *args: Any, theta: float | None = None) -> None: targets, theta = _split_targets_parameter(args, theta, "theta") self._interface.rz(targets, theta) + def r(self, addr0: int, axis_angle: float, theta: float) -> None: + """Apply a rotation about an axis in the X-Y plane to the specified qubit. + + ```math + R(\\phi, \\theta) = e^{-i \\frac{\\theta}{2} (\\cos\\phi\\, X + \\sin\\phi\\, Y)} + = R_Z(\\phi) R_X(\\theta) R_Z(-\\phi) + ``` + + Args: + addr0: The index of the target qubit. + axis_angle: The angle ``φ`` (in radians) of the rotation axis + within the X-Y plane, measured from the X-axis. + theta: The rotation angle in radians. + """ + self._interface.r(addr0, axis_angle, theta) + # Two qubit rotations def rxx(self, *args: Any, theta: float | None = None) -> None: """Apply RXX (Ising XX) rotation gates over consecutive qubit pairs. @@ -601,8 +617,23 @@ def rz(self, *args: Any, theta: float | None = None, truncate: bool = True) -> N targets, theta, truncate = _split_targets_parameter_truncate(args, theta, "theta", truncate) self._interface.rz(targets, theta, truncate) + def r(self, addr0: int, axis_angle: float, theta: float, *, truncate: bool = True) -> None: + """Apply a rotation about an axis in the X-Y plane to the specified qubit. + + See :meth:`RotationsMixin.r` for the gate definition. + + Args: + addr0: The index of the target qubit. + axis_angle: The angle ``φ`` (in radians) of the rotation axis + within the X-Y plane, measured from the X-axis. + theta: The rotation angle in radians. + truncate: See :meth:`rx`. + """ + self._interface.r(addr0, axis_angle, theta, truncate=truncate) + + # Two qubit rotations def rxx(self, *args: Any, theta: float | None = None, truncate: bool = True) -> None: - """Apply RXX (Ising XX) rotation gates over consecutive qubit pairs. + """Apply an RXX (Ising XX) rotation gate to two qubits. See `RotationsMixin.rxx` for the gate definition. diff --git a/ppvm-python/test/generalized_tableau/test_basics.py b/ppvm-python/test/generalized_tableau/test_basics.py index b0aa7747e..f90464379 100644 --- a/ppvm-python/test/generalized_tableau/test_basics.py +++ b/ppvm-python/test/generalized_tableau/test_basics.py @@ -155,6 +155,20 @@ def test_ry_pi_equals_y(): assert tab.measure(0) +def test_r_axis_zero_equals_rx(): + # r(axis_angle=0, θ=π) = rx(π), so |0> → |1> + tab = GeneralizedTableau(2) + tab.r(0, 0.0, math.pi) + assert tab.measure(0) + + +def test_r_axis_half_pi_equals_ry(): + # r(axis_angle=π/2, θ=π) = ry(π), so |0> → |1> + tab = GeneralizedTableau(2) + tab.r(0, math.pi / 2, math.pi) + assert tab.measure(0) + + def test_clifford_extensions_sqrt_x(): # sqrt_x followed by sqrt_x_dag is identity tab = GeneralizedTableau(2) diff --git a/ppvm-python/test/test_basics.py b/ppvm-python/test/test_basics.py index cd5f645b0..dd94f883e 100644 --- a/ppvm-python/test/test_basics.py +++ b/ppvm-python/test/test_basics.py @@ -191,6 +191,16 @@ def t(state): s.rz(0, theta=-PI / 2) assert pytest.approx(t(s).get("YI", 0.0)) == 1.0 + # r(axis_angle=0, θ=π/2) = rx(π/2): ZI → YI + s = PauliSum(n_qubits=2, initial_terms=["ZI"], coefficients=[1.0]) + s.r(0, 0.0, PI / 2) + assert pytest.approx(t(s).get("YI", 0.0)) == 1.0 + + # r(axis_angle=π/2, θ=π/2) = ry(π/2): ZI → −XI + s = PauliSum(n_qubits=2, initial_terms=["ZI"], coefficients=[1.0]) + s.r(0, PI / 2, PI / 2) + assert pytest.approx(t(s).get("XI", 0.0)) == -1.0 + # rxx(π/2): ZI → YX [cos·ZI + sin·YX at θ=π/2 → YX] s = PauliSum(n_qubits=2, initial_terms=["ZI"], coefficients=[1.0]) s.rxx(0, 1, theta=PI / 2) diff --git a/ppvm-python/uv.lock b/ppvm-python/uv.lock index 71d4c591e..d2f94dcd5 100644 --- a/ppvm-python/uv.lock +++ b/ppvm-python/uv.lock @@ -15,32 +15,13 @@ resolution-markers = [ "python_full_version < '3.11'", ] -[[package]] -name = "aiobotocore" -version = "3.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "aioitertools" }, - { name = "botocore" }, - { name = "jmespath" }, - { name = "multidict" }, - { name = "python-dateutil" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/75/42cce839c2ec263ff74b10b650fe36b066fbb124cbee6f247eac0983e1ab/aiobotocore-3.7.0.tar.gz", hash = "sha256:c64d871ed5491a6571948dd48eabd185b46c6c23b64e3afd0c059fc7593ada30", size = 127054, upload-time = "2026-05-09T10:02:52.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/5f/85535dfb3cfd6442d66d1df1694062c5d6df02f895329e7e120b2a3d2b8b/aiobotocore-3.7.0-py3-none-any.whl", hash = "sha256:680bde7c64679a821a9312641b759d9497f790ba8b2e88c6959e6273ee765b8e", size = 89539, upload-time = "2026-05-09T10:02:50.389Z" }, -] - [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, ] [[package]] @@ -180,15 +161,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, ] -[[package]] -name = "aioitertools" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322, upload-time = "2025-11-06T22:17:07.609Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182, upload-time = "2025-11-06T22:17:06.502Z" }, -] - [[package]] name = "aiosignal" version = "1.4.0" @@ -222,16 +194,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, ] [[package]] @@ -320,7 +292,7 @@ wheels = [ [[package]] name = "bloqade-circuit" -version = "0.14.2" +version = "0.14.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bloqade-decoders" }, @@ -331,16 +303,17 @@ dependencies = [ { name = "pyqrack-cpu" }, { name = "rich" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/07/3cb60f87c45b5b3d6e70cfd4607f534b6dd7447bf84c0cf22135b76f9911/bloqade_circuit-0.14.2.tar.gz", hash = "sha256:f385069887f17b2ba3c40c5ea301baead9e93eaa56e9e7ec71dc679e8ee58d5b", size = 421248, upload-time = "2026-05-12T09:41:52.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/c3/79b410d35cfcf9ae2222746e9c4b1b7e1878c2240dea7a471fa6096b30fa/bloqade_circuit-0.14.4.tar.gz", hash = "sha256:d6c71785d5805f8b91f50816c5d705c03f9d3e318be277199fe15a45a79e274d", size = 421472, upload-time = "2026-06-24T07:40:16.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/f5/67ae94c50f0e502cf30b586cebd3fdb1229d7bfa119d5eef40c0c801a0cd/bloqade_circuit-0.14.2-py3-none-any.whl", hash = "sha256:ad5b0474407a3a5b768eb20240e7bfa2406e6fdbc4658beb077136dbee681286", size = 254764, upload-time = "2026-05-12T09:41:50.802Z" }, + { url = "https://files.pythonhosted.org/packages/c2/84/de5fb96d2a1ee3452e9e4d9eeb93793cf9ebf9f328fff56573147a2f0339/bloqade_circuit-0.14.4-py3-none-any.whl", hash = "sha256:e86e9a6b173c0ed621017fa7677dc076fcef63a7cf8acd9adb2b776e54191631", size = 254869, upload-time = "2026-06-24T07:40:14.925Z" }, ] [[package]] name = "bloqade-decoders" -version = "0.5.1" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beliefmatching" }, @@ -349,46 +322,46 @@ dependencies = [ { name = "numpy" }, { name = "stim" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/6b/4cc21123a2884486ab69f2f1fa345f7e4a224a598f5c6a2e513eac6088e7/bloqade_decoders-0.5.1.tar.gz", hash = "sha256:142aff76c98dfb698ce4a13e0c5987614f5be3f8f540439e10cd064523409a7b", size = 137940, upload-time = "2026-03-04T15:57:32.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/3a/4f099731a0159572f3d0ffd2fca63406e5f55de412c4e1b26fb6e4e41c4e/bloqade_decoders-0.6.0.tar.gz", hash = "sha256:0579896f13b5089e1ba05e040a1f57b30c5f6acfc4cfb01b2ecf22ae63179f50", size = 152696, upload-time = "2026-06-17T14:09:50.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/a7f60dacebc1bb03293546849569764698be197fad6b785f08c6276f96f0/bloqade_decoders-0.5.1-py3-none-any.whl", hash = "sha256:ac58437519160548fd7b8ea8895be3f7d81d1d2097d60c0f3fd5d5442d499bf3", size = 17015, upload-time = "2026-03-04T15:57:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/935986caa8bc91a2be986a19f220ae894313a9ab9ebbcbeec18444358f49/bloqade_decoders-0.6.0-py3-none-any.whl", hash = "sha256:01cba5cdd02ce452e8252491fa9178b643fd9155413bd9c7d97a9b6052c7c571", size = 26409, upload-time = "2026-06-17T14:09:48.772Z" }, ] [[package]] name = "boto3" -version = "1.43.0" +version = "1.43.36" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/65/47670987f2f9e181397872c7ee6415b7b95156d711b7eab6c55f66e575bc/boto3-1.43.0.tar.gz", hash = "sha256:80d44a943ef90aba7958ab31d30c155c198acc8a9581b5846b3878b2c8951086", size = 113143, upload-time = "2026-04-29T22:07:49.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/9f/897287e955db0f50b12fd69ef45956e4fd2c7ddb48c736872f7ea2314443/boto3-1.43.36.tar.gz", hash = "sha256:587d7ee92a12e440ad12b0e7f11f3358f0c4d65b19f64726efc94aaf194aff28", size = 112690, upload-time = "2026-06-23T02:47:14.561Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/a0/3e6a0b1c1ea6bec76f71473727ef27abf3cd40e9709b3ebcbfbcfaae6f79/boto3-1.43.0-py3-none-any.whl", hash = "sha256:8ebe03754a4b73a5cb6ec2f14cca03ac33bd4760d0adea53da4724845130258b", size = 140497, upload-time = "2026-04-29T22:07:46.216Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f1/274303f52483ecf199eae6f8d9b6f5951670397ee4d72c06cfd4eb644612/boto3-1.43.36-py3-none-any.whl", hash = "sha256:42942dde254673abcbc9e6e60017c88341a4f49d99d24e1f2e290fb38138c26f", size = 140031, upload-time = "2026-06-23T02:47:13.178Z" }, ] [[package]] name = "botocore" -version = "1.43.0" +version = "1.43.36" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/79/2f4be1896db3db7ccf44504253a175d56b6bd6b669619edc5147d1aa21ea/botocore-1.43.0.tar.gz", hash = "sha256:e933b31a2d644253e1d029d7d39e99ba41b87e29300534f189744cc438cdf928", size = 15286817, upload-time = "2026-04-29T22:07:31.723Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/37/da9e7f6ca73ac73afd7f0bb7f238aa5daba35c081e98d7f48a7c399599c0/botocore-1.43.36.tar.gz", hash = "sha256:4cae47d1b2d426316b85a0087d9e69e048f13bc003b5177d74639fe9dfd28205", size = 15625488, upload-time = "2026-06-23T02:47:03.192Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/4b/afc1fef8a43bafb139f57f73bbd70df82807af5934321e8112ae50668827/botocore-1.43.0-py3-none-any.whl", hash = "sha256:cc5b15eaec3c6eac05d8012cb5ef17ebe891beb88a16ca13c374bfaece1241e6", size = 14970102, upload-time = "2026-04-29T22:07:27Z" }, + { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, ] [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] [[package]] @@ -580,14 +553,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.2" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, ] [[package]] @@ -774,62 +747,59 @@ wheels = [ [[package]] name = "cryptography" -version = "48.0.1" +version = "49.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", size = 8008324, upload-time = "2026-06-09T22:31:00.702Z" }, - { url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" }, - { url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" }, - { url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" }, - { url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" }, - { url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" }, - { url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" }, - { url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" }, - { url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" }, - { url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" }, - { url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" }, - { url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" }, - { url = "https://files.pythonhosted.org/packages/b7/a0/8f50cae9c74e718ed769d63ed5c74bd0ea830c9550a74629cebd1b9c7bc7/cryptography-48.0.1-cp311-abi3-win32.whl", hash = "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", size = 3304154, upload-time = "2026-06-09T22:32:16.845Z" }, - { url = "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", size = 3817138, upload-time = "2026-06-09T22:32:00.388Z" }, - { url = "https://files.pythonhosted.org/packages/42/06/3e768b4c3bc78201583fa35a0e18f640dd782ff41afba88f8545481a8874/cryptography-48.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", size = 7989830, upload-time = "2026-06-09T22:31:07.8Z" }, - { url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" }, - { url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" }, - { url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" }, - { url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" }, - { url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" }, - { url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" }, - { url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" }, - { url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" }, - { url = "https://files.pythonhosted.org/packages/a4/27/728c77876f12b000820b69ae490f3c4083775e79e07827e9e60be07ad209/cryptography-48.0.1-cp314-cp314t-win32.whl", hash = "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", size = 3278498, upload-time = "2026-06-09T22:31:29.154Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/79a612c6d7b1e6ee0edd43633d53035bec2cfb78c82b76f7864f39e36f34/cryptography-48.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", size = 3798790, upload-time = "2026-06-09T22:31:56.697Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6c/00fa2a95997164c8b2072ce327c23d4ab20809ccc323ea5fab91e53a4bba/cryptography-48.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", size = 7987408, upload-time = "2026-06-09T22:32:20.777Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" }, - { url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" }, - { url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" }, - { url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" }, - { url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" }, - { url = "https://files.pythonhosted.org/packages/36/bf/ed70785c496e89d7e73b7cda2d21f2447fd6d4e821714b8d04ff217fed92/cryptography-48.0.1-cp39-abi3-win32.whl", hash = "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", size = 3282307, upload-time = "2026-06-09T22:30:53.162Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ff/371ea7d252656ee1eb6d83eeeef3d1d0c6baf1d6497687d081ea03814670/cryptography-48.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", size = 3793408, upload-time = "2026-06-09T22:32:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d3/eb4e394e587341fdad09a09101fa76478ead3a78b0ad63e55c22f0d75c02/cryptography-48.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:08a597acce1ff37f347400087776599e2348a3a8bc53b44120e463cd274efe4a", size = 3951747, upload-time = "2026-06-09T22:31:23.871Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4a/3f43451b4f858bfceaaaffc649e6e787e8d4fb332a1d443af39ab02cc8f1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:735824ec41b7f74a7c45fb1591349333e4c696cb6c044e5f46356e560143e4cd", size = 4641226, upload-time = "2026-06-09T22:31:02.532Z" }, - { url = "https://files.pythonhosted.org/packages/73/4e/855584c2c23b09e4ce2d3b9c30e983e679cd60b068c513c6bbdb91e11782/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:92a46e1d638daa264ba2971c0b0489c9409787943efae4d60ffda3d091ef832c", size = 4668958, upload-time = "2026-06-09T22:32:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/42/3b/d35750e41d803d1e516fd6d6011f065424924da7af1748cef4cc9cb3ede1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7e234ac052af99f2700826a5c29ea99d9c1b1f80341cde62d11c8154dc8e0bd9", size = 4640793, upload-time = "2026-06-09T22:32:26.331Z" }, - { url = "https://files.pythonhosted.org/packages/ca/aa/cdb7181fe865285e87e96825aaab239400f1de0c3bfba9bd9769b79f1a92/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:33842cf0888951cef5bc7ac724ab844a42044c1727b967b7f8997289a0464f92", size = 4668505, upload-time = "2026-06-09T22:31:27.534Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8c/ce3823c06c2804f194f9e64f0d67fa3f4094a39f2bb1a990cd03603af8fc/cryptography-48.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6184ca7b174f28d7c703f1290d4b297217c45355f77a98f67e9b7f14549ac54a", size = 3742204, upload-time = "2026-06-09T22:31:34.773Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, + { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, + { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, ] [[package]] @@ -843,40 +813,40 @@ wheels = [ [[package]] name = "debugpy" -version = "1.8.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/be/8bd693a0b9d53d48c8978fa5d889e06f3b5b03e45fd1ea1e78267b4887cb/debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64", size = 2099192, upload-time = "2026-01-29T23:03:29.707Z" }, - { url = "https://files.pythonhosted.org/packages/77/1b/85326d07432086a06361d493d2743edd0c4fc2ef62162be7f8618441ac37/debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642", size = 3088568, upload-time = "2026-01-29T23:03:31.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/60/3e08462ee3eccd10998853eb35947c416e446bfe2bc37dbb886b9044586c/debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2", size = 5284399, upload-time = "2026-01-29T23:03:33.678Z" }, - { url = "https://files.pythonhosted.org/packages/72/43/09d49106e770fe558ced5e80df2e3c2ebee10e576eda155dcc5670473663/debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893", size = 5316388, upload-time = "2026-01-29T23:03:35.095Z" }, - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, - { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, - { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, - { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, - { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +version = "1.8.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f3/6b1d4c71f4cbb5360009f928934a03b42906f28fc7b3f7f35f04e58acead/debugpy-1.8.21-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:8eeab7b5462f683452c57c0126aaa5ec4e974ddb705f39ba87dff8818c8e08f9", size = 2113873, upload-time = "2026-06-01T19:30:37.148Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f2/17c3bf91cebc173bfbf5734cd2669723d0a35c0cf9d2fd2124546efeae83/debugpy-1.8.21-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:0fddfdc130ac6d8bfc0415b0409822fa901c8f310e5c945ac5653a0352532344", size = 3004715, upload-time = "2026-06-01T19:30:38.888Z" }, + { url = "https://files.pythonhosted.org/packages/5a/22/1f8efd80c7b5909e760f9cfd0c9e8681d2d35d532f7c0a40760cd4da4a19/debugpy-1.8.21-cp310-cp310-win32.whl", hash = "sha256:72b5d676c4cbfac3bac5bb01c138a4656e843f93f03ce2a5f4e394ad49fbee73", size = 5303455, upload-time = "2026-06-01T19:30:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/da/ce/54c79abd6cccef92fa7b43d97e3acafedf4d645557267ece05e948b5e4b8/debugpy-1.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a7fe47fd23da57b9e0bec3f4a8ee65a2dc55782455ed7f2141d75ab5d2eaeef5", size = 5331751, upload-time = "2026-06-01T19:30:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, + { url = "https://files.pythonhosted.org/packages/77/6b/d817e1f8cc77aa055d37fba092e0febfdff40fe652d8d53d4cd7a86ad98d/debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88", size = 2477398, upload-time = "2026-06-01T19:30:57.644Z" }, + { url = "https://files.pythonhosted.org/packages/48/57/412421516afc3055fa577516f00beec3d663f9b0ab330639547ae6c57720/debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2", size = 3962096, upload-time = "2026-06-01T19:30:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2c616337cf6ba7b07ebbc97f02c6c945a8e2f76b365e33ee809c32ee36d1/debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1", size = 5336288, upload-time = "2026-06-01T19:31:00.79Z" }, + { url = "https://files.pythonhosted.org/packages/f8/99/9175103392f84c4b1bf7622888cdc68da07f0ff7d9e581266428f6776033/debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0", size = 5376567, upload-time = "2026-06-01T19:31:02.56Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3d/f4bbb323a548bfab2af3d6b4ffd9bf22636e55956a1285d317a1de643aad/debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782", size = 2477209, upload-time = "2026-06-01T19:31:04.157Z" }, + { url = "https://files.pythonhosted.org/packages/8c/2d/6e7ec524984a1702777868de49a4c53202bddac2a432a76a093469587750/debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e", size = 3927115, upload-time = "2026-06-01T19:31:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/97/47/d1aa6d64005a98a9144647d99306b419396f9ad7bf1d73c119e17a81fb4d/debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c", size = 5336724, upload-time = "2026-06-01T19:31:07.711Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/b905b90d163af11878c1af8abafa4a25206335e112e284e413454543a6da/debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8", size = 5373803, upload-time = "2026-06-01T19:31:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, ] [[package]] name = "decorator" -version = "5.2.1" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] [[package]] @@ -893,7 +863,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -911,7 +881,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.1" +version = "0.138.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -920,66 +890,66 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/58/ff455d9fe47c60abadb34b9e05a304b1f05f5ab8000ac01565156b6f5e43/fastapi-0.138.0.tar.gz", hash = "sha256:d445a4877636ad191e7053e08c9bf98cb921a6756776848400bb773d1740c061", size = 419240, upload-time = "2026-06-20T01:18:05.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ff/8496d9847a5fedae775eb49460722d3efaa80487854273e9647ae876218c/fastapi-0.138.0-py3-none-any.whl", hash = "sha256:b6f54fd1bd72c80b0f899f172c61a600f6f7af9b43d4d772a018f35624048cb0", size = 126779, upload-time = "2026-06-20T01:18:03.483Z" }, ] [[package]] name = "fonttools" -version = "4.62.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, - { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, - { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, - { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, - { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, - { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, ] [[package]] @@ -1105,16 +1075,16 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.4.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, ] [[package]] name = "gcsfs" -version = "2026.4.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1126,14 +1096,14 @@ dependencies = [ { name = "google-cloud-storage-control" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/0bd3f6796422cfed6be1949c44668b386400f231d63ab30aefa6165baf0f/gcsfs-2026.4.0.tar.gz", hash = "sha256:f3d80dd1c98737798bc84472e6ff9c59873bd17a26e170d46ce90b3118db4390", size = 1139035, upload-time = "2026-04-29T21:04:11.64Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/ad/36777b2030b96d32f5c557606ac11bf5d0e152620fc15055bd1e30194037/gcsfs-2026.6.0.tar.gz", hash = "sha256:bfb1f912b3f51006b00bcd5fcef915214cb51f8b892a3974178430a55990ba3f", size = 1007987, upload-time = "2026-06-18T03:16:08.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/37/3922951a55a3d0f0340e884929087ce08e333cbb16a86002535c095960fc/gcsfs-2026.4.0-py3-none-any.whl", hash = "sha256:d9e838834d8cce6cb623c6a6a5fad66a4d122dc5c609d4b1c1977b55f759dcc5", size = 72190, upload-time = "2026-04-29T21:04:09.997Z" }, + { url = "https://files.pythonhosted.org/packages/69/43/a0096d96d3271640bbda056b9d22249be141a54509859b99002ee6149a01/gcsfs-2026.6.0-py3-none-any.whl", hash = "sha256:4a81fe40fbdef450aeaacb534b42c04059f8f3b080e3e169137247a5bb023464", size = 89629, upload-time = "2026-06-18T03:16:07.407Z" }, ] [[package]] name = "google-api-core" -version = "2.30.3" +version = "2.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -1142,9 +1112,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/22/155cadf1d49272a9cf48f3168c0f3874fa13397297e611a5ea00cd093880/google_api_core-2.31.0.tar.gz", hash = "sha256:2be84ee0f584c48e6bde1b36766e23348b361fb7e55e56135fc76ce1c397f9c2", size = 176492, upload-time = "2026-06-03T14:52:17.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, + { url = "https://files.pythonhosted.org/packages/86/40/9bdbb60b03a332bd45acb8703da08bbc27d991d35286b62e42acc86d243a/google_api_core-2.31.0-py3-none-any.whl", hash = "sha256:ef79fb3784c71cbac89cbd03301ba0c8fb8ad2aa95d7f9204dd9628f7adf59ab", size = 173102, upload-time = "2026-06-03T14:51:26.729Z" }, ] [package.optional-dependencies] @@ -1155,15 +1125,15 @@ grpc = [ [[package]] name = "google-auth" -version = "2.53.0" +version = "2.55.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/ad/ff781329bbbdc0974a098d996e89c9e1f7024262f9e3eec442fbb9ad1ac6/google_auth-2.53.0.tar.gz", hash = "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c", size = 335844, upload-time = "2026-05-15T20:53:07.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/1c/70b23fc52b2bb3c70b379f3bd05c4a60ab3a873e30c6bd21c57e0154848a/google_auth-2.55.0.tar.gz", hash = "sha256:fcd3a130f575fa36403d38774af1c64a4fbfbca09215f0589d2372b5119697cb", size = 349379, upload-time = "2026-06-15T22:33:16.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/c9/db44165ba7c581268c6d46017ef63339110378305062830104fc7fa144cb/google_auth-2.53.0-py3-none-any.whl", hash = "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68", size = 246071, upload-time = "2026-05-15T20:53:05.609Z" }, + { url = "https://files.pythonhosted.org/packages/44/71/c0321dc6d63d99946da45f7c06299b934e4f7f7da5c4f14d101bcb39adf1/google_auth-2.55.0-py3-none-any.whl", hash = "sha256:a17cef9dedf98c4ebae2fb0c48c8f75952c877cbc2efe09f329ef16c2783d88a", size = 252400, upload-time = "2026-06-15T22:33:14.992Z" }, ] [[package]] @@ -1194,7 +1164,7 @@ wheels = [ [[package]] name = "google-cloud-storage" -version = "3.10.1" +version = "3.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -1204,14 +1174,14 @@ dependencies = [ { name = "google-resumable-media" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/47/205eb8e9a1739b5345843e5a425775cbdc472cc38e7eda082ba5b8d02450/google_cloud_storage-3.10.1.tar.gz", hash = "sha256:97db9aa4460727982040edd2bd13ff3d5e2260b5331ad22895802da1fc2a5286", size = 17309950, upload-time = "2026-03-23T09:35:23.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/72/86f94e1639a8bcd9d33e8e01b49afcaa1c3a13bda7683c681717e0901e15/google_cloud_storage-3.12.0.tar.gz", hash = "sha256:03ae9847c6babb368f35f054126b8a08cbc0e3266efb990eb17b9926a45cf3be", size = 17338620, upload-time = "2026-06-12T18:03:29.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/ff/ca9ab2417fa913d75aae38bf40bf856bb2749a604b2e0f701b37cfcd23cc/google_cloud_storage-3.10.1-py3-none-any.whl", hash = "sha256:a72f656759b7b99bda700f901adcb3425a828d4a29f911bc26b3ea79c5b1217f", size = 324453, upload-time = "2026-03-23T09:35:21.368Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/a89eaebd2f9db5f92ddcc8e4f23c266be1dbd11058bb83451d8dd029f34c/google_cloud_storage-3.12.0-py3-none-any.whl", hash = "sha256:3880773754ddf7c27567b04e2a4d193950b6b99429f37b9097d873686e95b09c", size = 340605, upload-time = "2026-06-12T18:03:12.677Z" }, ] [[package]] name = "google-cloud-storage-control" -version = "1.11.0" +version = "1.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core", extra = ["grpc"] }, @@ -1221,9 +1191,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/4d/f36795a1cb37562caaf7fa9367ed34af29a09fd28ccda36f2f62c686de8f/google_cloud_storage_control-1.11.0.tar.gz", hash = "sha256:e97a0f3c99bc5ec5a2a770298ca9e8d5feeaa41bb55f2f2d7b36df2b817ee950", size = 117255, upload-time = "2026-03-26T22:18:32.645Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/ae/707995271f77e3c1da715c740160f98f87a79a5c601b28d17c4d2500c037/google_cloud_storage_control-1.12.0.tar.gz", hash = "sha256:49090d03532c0c84c6246a5fd490c31fd4bbffb4c7771c0a6744ec23a194a5f6", size = 137036, upload-time = "2026-06-03T16:14:00.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/aa/4df4066f4883afdae530ad95362f769cd9379fd2fb2d91f819aa7c9bdcc0/google_cloud_storage_control-1.11.0-py3-none-any.whl", hash = "sha256:98d2a3be8ab124ae09c33c1b8e4aadc68597c171c60c33e62666bcc74493f8ee", size = 89728, upload-time = "2026-03-26T22:16:17.2Z" }, + { url = "https://files.pythonhosted.org/packages/34/ba/f187f30fb2c8bb5dbb0de0fcb0dec2f8fab0b782ab42facc83ea02628f1b/google_cloud_storage_control-1.12.0-py3-none-any.whl", hash = "sha256:20f7f1252fa5635d47e12f746aa69d719e31eb9405cbbd14cdfba317db27d421", size = 102205, upload-time = "2026-06-03T16:12:43.266Z" }, ] [[package]] @@ -1263,14 +1233,14 @@ wheels = [ [[package]] name = "google-resumable-media" -version = "2.9.0" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-crc32c" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/4b/0b235beccc310d0a48adbc7246b719d173cca6c88c572dfa4b090e39143c/google_resumable_media-2.9.0.tar.gz", hash = "sha256:f7cfb224846a9dd444d125115dfbe8ef02a2b893e78f087762fe716a255a734b", size = 2164534, upload-time = "2026-05-07T08:04:44.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/f8/1ca5781d6be9cb9f73f7d40f4958c4bd1226a60598e3e39e1d6aaf838c4b/google_resumable_media-2.10.0.tar.gz", hash = "sha256:e324bc9d0fdae4c52a08ae90456edc4e71ece858399e1217ac0eb3a51d6bc6ee", size = 2164570, upload-time = "2026-06-03T16:14:26.103Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/73/3518e63deb1667c5409a4579e28daf5e84479a87a72c547e0487f7883dcd/google_resumable_media-2.9.0-py3-none-any.whl", hash = "sha256:c8901e88e389af8bed64d9696c74d8bad961865eb2236e13e0bfca9bb0a65ca3", size = 81507, upload-time = "2026-05-07T08:03:23.809Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d8/00c6854ac1512bb9eaf13bd3f8f28222f7674947fc510a4ff7616f2efc80/google_resumable_media-2.10.0-py3-none-any.whl", hash = "sha256:88152884bee37b2bf36a0ab81ad8c7fd12212c9803dd981d77c1b35b02d34e7c", size = 81533, upload-time = "2026-06-03T16:13:12.51Z" }, ] [[package]] @@ -1306,77 +1276,77 @@ wheels = [ [[package]] name = "grpcio" -version = "1.80.0" +version = "1.81.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/cd/bb7b7e54084a344c03d68144450da7ddd5564e51a298ae1662de65f48e2d/grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c", size = 6050363, upload-time = "2026-03-30T08:46:20.894Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/1417f5c3460dea65f7a2e3c14e8b31e77f7ffb730e9bfadd89eda7a9f477/grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388", size = 12026037, upload-time = "2026-03-30T08:46:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/43/98/c910254eedf2cae368d78336a2de0678e66a7317d27c02522392f949b5c6/grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02", size = 6602306, upload-time = "2026-03-30T08:46:27.593Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f8/88ca4e78c077b2b2113d95da1e1ab43efd43d723c9a0397d26529c2c1a56/grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc", size = 7301535, upload-time = "2026-03-30T08:46:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f9/96/f28660fe2fe0f153288bf4a04e4910b7309d442395135c88ed4f5b3b8b40/grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a", size = 6808669, upload-time = "2026-03-30T08:46:31.984Z" }, - { url = "https://files.pythonhosted.org/packages/47/eb/3f68a5e955779c00aeef23850e019c1c1d0e032d90633ba49c01ad5a96e0/grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9", size = 7409489, upload-time = "2026-03-30T08:46:34.684Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a7/d2f681a4bfb881be40659a309771f3bdfbfdb1190619442816c3f0ffc079/grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199", size = 8423167, upload-time = "2026-03-30T08:46:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/97/8a/29b4589c204959aa35ce5708400a05bba72181807c45c47b3ec000c39333/grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81", size = 7846761, upload-time = "2026-03-30T08:46:40.091Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d2/ed143e097230ee121ac5848f6ff14372dba91289b10b536d54fb1b7cbae7/grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069", size = 4156534, upload-time = "2026-03-30T08:46:42.026Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c9/df8279bb49b29409995e95efa85b72973d62f8aeff89abee58c91f393710/grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58", size = 4889869, upload-time = "2026-03-30T08:46:44.219Z" }, - { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" }, - { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" }, - { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" }, - { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/d5/f2b159d8eec08be2a855ef698f5b6f7f9fdda022e4dd9e4f5d968affd678/grpcio-1.81.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:6f9a0c9c1cc15c112d1c053064fd032b64917062292c3d70aea280e02ae10b77", size = 6086868, upload-time = "2026-06-11T12:44:19.364Z" }, + { url = "https://files.pythonhosted.org/packages/80/41/9c95232b94b219ed8b14029d9cd000e0381cafba869c451dda60af84f4ba/grpcio-1.81.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:69ef28e54fc85397f91b8c19592b8ef3d81952080366914823bd8572a2958120", size = 12062291, upload-time = "2026-06-11T12:44:27.142Z" }, + { url = "https://files.pythonhosted.org/packages/83/8b/bd9284bdd665ddf877a3e8bc2930d1bcf6ebdbae7b0da5c783dc26bd6e33/grpcio-1.81.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15641444eca4a29358107b3dceb74c1c6305c55c822fd199b458aaea4068a7fb", size = 6635242, upload-time = "2026-06-11T12:44:30.741Z" }, + { url = "https://files.pythonhosted.org/packages/60/24/78fa025517a925f1a17da71c4ef9d5f1c6f9fa65af22dfb523c5c6317a21/grpcio-1.81.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d4b2dddfc219f54f956ccd53cf76a1d338ffe68fc7f2849ec9c7feb9927ff692", size = 7332974, upload-time = "2026-06-11T12:44:33.72Z" }, + { url = "https://files.pythonhosted.org/packages/f7/11/402295b388dd35861007f8a26a37c2e2f284212d57bdf407c31f36043746/grpcio-1.81.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca1cc11d82677b9662082e5478b7528e2b7db7beaa6bdff42bd62789d81be399", size = 6836597, upload-time = "2026-06-11T12:44:36.108Z" }, + { url = "https://files.pythonhosted.org/packages/4d/71/37b10fd4fd579ffade6e695c14e9df5e8cba9e2365b81c131da438b67c34/grpcio-1.81.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa2ba7d2ad6df4d80127cea65e5b8d5e2c3adbf153ff4804452836328aca7c54", size = 7440660, upload-time = "2026-06-11T12:44:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d5/40203f828abc83d458b634666df6df13778032f178c03845ad5a93682388/grpcio-1.81.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:592b5fee597faa91cce2dd294dd7d9a1c83d76c4dbf877e33ec1adb866b2fbed", size = 8443171, upload-time = "2026-06-11T12:44:41.678Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2c/0ed82ea35b5ec595e10444940c1db8c0e0ef57aa46bc8797d5ff838a219e/grpcio-1.81.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62481553b1793a27e9b9c3cf9e5bd483ef045ca72462592074b46d42b0c4d9b9", size = 7868905, upload-time = "2026-06-11T12:44:44.854Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/dcbdc1a68a07cc2b631c3098953794f17d75f93426a019240b90ce5423d6/grpcio-1.81.1-cp310-cp310-win32.whl", hash = "sha256:bb693b1e3d9a2f3fd228e2110daf4b5aeedb36761ca1e4282f74725f6d89f611", size = 4202215, upload-time = "2026-06-11T12:44:47.165Z" }, + { url = "https://files.pythonhosted.org/packages/75/a1/d7ab9f1f42efcb7d9e6111d38be6b367737a72ea2c534e1f55c81e1b6436/grpcio-1.81.1-cp310-cp310-win_amd64.whl", hash = "sha256:88268ca418cacea64cecb0d1d600d3c6b3a8038fcba02e1e205178c5b1f47661", size = 4936582, upload-time = "2026-06-11T12:44:49.479Z" }, + { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" }, + { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" }, + { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" }, + { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" }, + { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, + { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, ] [[package]] name = "grpcio-status" -version = "1.80.0" +version = "1.81.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/ed/105f619bdd00cb47a49aa2feea6232ea2bbb04199d52a22cc6a7d603b5cb/grpcio_status-1.80.0.tar.gz", hash = "sha256:df73802a4c89a3ea88aa2aff971e886fccce162bc2e6511408b3d67a144381cd", size = 13901, upload-time = "2026-03-30T08:54:34.784Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/26/0aa9168c87882381fd810d140c279a2490ed6aee655f0515d6f56c5ca404/grpcio_status-1.81.1.tar.gz", hash = "sha256:9389a03e746017b10f0630c064289201458f3ce01f5d7ef4b0bebc1ef6cf82ad", size = 13923, upload-time = "2026-06-11T12:58:48.636Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/80/58cd2dfc19a07d022abe44bde7c365627f6c7cb6f692ada6c65ca437d09a/grpcio_status-1.80.0-py3-none-any.whl", hash = "sha256:4b56990363af50dbf2c2ebb80f1967185c07d87aa25aa2bea45ddb75fc181dbe", size = 14638, upload-time = "2026-03-30T08:54:01.569Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5e/5abfec5f7e89d3b7993d57cfb025ca5f968a2c18656d7fcda2b6919440b9/grpcio_status-1.81.1-py3-none-any.whl", hash = "sha256:08072fa9995f4a95c647fc6f4f85e2411573d00087bcabdf30f260114338f232", size = 14638, upload-time = "2026-06-11T12:58:31.982Z" }, ] [[package]] @@ -1390,11 +1360,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1420,28 +1390,27 @@ wheels = [ [[package]] name = "ipykernel" -version = "7.2.0" +version = "7.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython", version = "9.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, + { name = "nest-asyncio2" }, { name = "packaging" }, { name = "psutil" }, { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" }, ] [[package]] @@ -1483,32 +1452,7 @@ wheels = [ [[package]] name = "ipython" -version = "9.10.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.11.*'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version == '3.11.*'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version == '3.11.*'" }, - { name = "jedi", marker = "python_full_version == '3.11.*'" }, - { name = "matplotlib-inline", marker = "python_full_version == '3.11.*'" }, - { name = "pexpect", marker = "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version == '3.11.*'" }, - { name = "pygments", marker = "python_full_version == '3.11.*'" }, - { name = "stack-data", marker = "python_full_version == '3.11.*'" }, - { name = "traitlets", marker = "python_full_version == '3.11.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232", size = 622827, upload-time = "2026-03-27T09:53:24.566Z" }, -] - -[[package]] -name = "ipython" -version = "9.12.0" +version = "9.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -1520,22 +1464,25 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, - { name = "jedi", marker = "python_full_version >= '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, - { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "stack-data", marker = "python_full_version >= '3.12'" }, - { name = "traitlets", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, ] [[package]] @@ -1557,8 +1504,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython", version = "9.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1606,14 +1552,14 @@ wheels = [ [[package]] name = "jedi" -version = "0.19.2" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, ] [[package]] @@ -1648,16 +1594,16 @@ wheels = [ [[package]] name = "json5" -version = "0.14.0" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7d/05c46a96a78147ae3bf99c2f4169ce144a70220b8d6fcd56f6ec368b8ce9/json5-0.15.0.tar.gz", hash = "sha256:7424d1f1eb1d56da6e3d70643f53619862b4ce81440bdb8ecfd6f875e5ba4a71", size = 53278, upload-time = "2026-06-19T20:08:27.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, + { url = "https://files.pythonhosted.org/packages/eb/be/59527c99478aade6bb33a68d72e6e18dd4e6ff6eacfc7d01bdb15bc76912/json5-0.15.0-py3-none-any.whl", hash = "sha256:56636a30c0e8a4665fe2179c0212f32eae3796dea89ea6f649b9436ecdb39618", size = 36570, upload-time = "2026-06-19T20:08:26.748Z" }, ] [[package]] name = "jupyter-client" -version = "8.8.0" +version = "8.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, @@ -1665,10 +1611,11 @@ dependencies = [ { name = "pyzmq" }, { name = "tornado" }, { name = "traitlets" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" }, ] [[package]] @@ -1888,7 +1835,8 @@ dependencies = [ { name = "pymatching" }, { name = "pytest" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sinter" }, { name = "stim" }, { name = "tqdm" }, @@ -1937,14 +1885,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] @@ -2034,88 +1982,166 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.8" +version = "3.10.9" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] dependencies = [ { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, + { name = "cycler", marker = "python_full_version < '3.11'" }, + { name = "fonttools", marker = "python_full_version < '3.11'" }, + { name = "kiwisolver", marker = "python_full_version < '3.11'" }, + { name = "numpy", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "pyparsing", marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, - { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, - { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, + +[[package]] +name = "matplotlib" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.11'" }, + { name = "fonttools", marker = "python_full_version >= '3.11'" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "pyparsing", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261, upload-time = "2026-06-12T02:27:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669, upload-time = "2026-06-12T02:27:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076, upload-time = "2026-06-12T02:27:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999, upload-time = "2026-06-12T02:27:42.433Z" }, + { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103, upload-time = "2026-06-12T02:27:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945, upload-time = "2026-06-12T02:27:46.867Z" }, + { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304, upload-time = "2026-06-12T02:27:48.798Z" }, + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532, upload-time = "2026-06-12T02:28:06.769Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760, upload-time = "2026-06-12T02:28:09.152Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623, upload-time = "2026-06-12T02:28:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372, upload-time = "2026-06-12T02:28:14.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099, upload-time = "2026-06-12T02:28:16.383Z" }, + { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727, upload-time = "2026-06-12T02:28:18.581Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506, upload-time = "2026-06-12T02:28:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838, upload-time = "2026-06-12T02:28:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298, upload-time = "2026-06-12T02:28:24.796Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491, upload-time = "2026-06-12T02:28:27.141Z" }, + { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059, upload-time = "2026-06-12T02:28:29.222Z" }, + { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576, upload-time = "2026-06-12T02:28:31.66Z" }, + { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519, upload-time = "2026-06-12T02:28:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456, upload-time = "2026-06-12T02:28:35.878Z" }, + { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137, upload-time = "2026-06-12T02:28:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514, upload-time = "2026-06-12T02:28:40.028Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005, upload-time = "2026-06-12T02:28:42.39Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459, upload-time = "2026-06-12T02:28:44.483Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160, upload-time = "2026-06-12T02:28:46.564Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186, upload-time = "2026-06-12T02:28:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349, upload-time = "2026-06-12T02:28:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921, upload-time = "2026-06-12T02:28:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190, upload-time = "2026-06-12T02:28:55.623Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181, upload-time = "2026-06-12T02:28:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715, upload-time = "2026-06-12T02:29:00.555Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427, upload-time = "2026-06-12T02:29:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809, upload-time = "2026-06-12T02:29:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226, upload-time = "2026-06-12T02:29:07.033Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094, upload-time = "2026-06-12T02:29:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183, upload-time = "2026-06-12T02:29:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, ] [[package]] name = "matplotlib-inline" -version = "0.2.1" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, ] [[package]] @@ -2129,11 +2155,11 @@ wheels = [ [[package]] name = "more-itertools" -version = "11.0.2" +version = "11.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, ] [[package]] @@ -2275,12 +2301,12 @@ wheels = [ ] [[package]] -name = "nest-asyncio" -version = "1.6.0" +name = "nest-asyncio2" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" }, ] [[package]] @@ -2389,11 +2415,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -2459,11 +2485,11 @@ wheels = [ [[package]] name = "parso" -version = "0.8.6" +version = "0.8.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] [[package]] @@ -2578,11 +2604,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -2777,17 +2803,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.6" +version = "7.35.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, ] [[package]] @@ -3009,34 +3035,40 @@ wheels = [ [[package]] name = "pymatching" -version = "2.3.1" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "matplotlib" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/92/60f1419500b49afb0925b0536538c352ef71b4bf182c28b0e3861141fc81/pymatching-2.3.1.tar.gz", hash = "sha256:dac1afab0a190a003a3689a18c3afc5c99a5beaa6a099d79e3d4c8b24c1fa7c5", size = 347052, upload-time = "2025-09-25T21:46:25.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/65/4e414c1a09b198b5ab771a9249cbbfe47c29951914e1393f787aaf8041b4/pymatching-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f3bd434808ce6dc59730bed2fbb22bb2cc5454f13f4833ba621240e0d75d4f6", size = 380012, upload-time = "2025-09-25T21:45:57.439Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4b/abc76305247b2204eb2a72239c3032647a31d69eb99ca63181027011d7a7/pymatching-2.3.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:403b4abba5d54ea8012a5d7334a8f6ba871f72875e072753bb7078e81a4f32e4", size = 411642, upload-time = "2025-09-25T21:45:59.018Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3c/ecdc80293a45b4702a8fdb0630ebca9e93efd91a3b6b6986a81be9a5a3a1/pymatching-2.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba6c1320dcad0268073cbba3b5e7296d94c61944a798cf8674cbaef68d9af91", size = 623778, upload-time = "2025-09-25T21:46:00.058Z" }, - { url = "https://files.pythonhosted.org/packages/ce/22/6757dc12b3adb5fc97adc414bee2fe63ce66fb534548288ef2d7e6b326f0/pymatching-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:b902eab5d530990eb577eabafc8969115252baa48eb9e9c7734b6bca97edc98c", size = 345417, upload-time = "2025-09-25T21:46:01.281Z" }, - { url = "https://files.pythonhosted.org/packages/9b/94/eee399cae91c349481467b03189832277ea79781f331cc04bde51834a1e1/pymatching-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:020a7bd1f8a2d312fe98f4eee594cff98395272ab868f93ee202adbdec1901bf", size = 381265, upload-time = "2025-09-25T21:46:02.262Z" }, - { url = "https://files.pythonhosted.org/packages/42/24/a72c64d30b7f1a86b3b87bd358b01c00eb2a8da8ceda9d3e4b030f73f26e/pymatching-2.3.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:10699180d84ec27a6bf5ccd78c61b8aa52afff0f01263cf928a51ec876138037", size = 412928, upload-time = "2025-09-25T21:46:03.603Z" }, - { url = "https://files.pythonhosted.org/packages/24/dc/946e33cdc7aa734913f2c97326ddcea0f0fdd3cfdcd7b986e5326f5d3ee3/pymatching-2.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d462371c7d9864fb1b7993018f16e2f105d732e5671c56dd14c9a789a1aa4861", size = 624637, upload-time = "2025-09-25T21:46:05.025Z" }, - { url = "https://files.pythonhosted.org/packages/56/97/14767cb0cb81de1cd2fc3211072bc3bdd08d0d4ae054eafa5584273a6a53/pymatching-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d27bd5cf79b25268dfce11fb9e3a47acd0a5c1f88815c2fab333eaa8bd45b7b", size = 346047, upload-time = "2025-09-25T21:46:05.991Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/cf2302b391f7c692feed5c1cddb07c2667722dd2944232cfb4c1a691e2ca/pymatching-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bde7bac71e3b1b1d55569c664a7a2d0b0d87962df12b9b45ce3e287d0ac0dff", size = 380195, upload-time = "2025-09-25T21:46:07.26Z" }, - { url = "https://files.pythonhosted.org/packages/75/60/90c3fd4fe1b16dfc46aa3a774c9dc0438299aa07e7d9f41c3f6bbd207734/pymatching-2.3.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:fb06a9a60c32c490f2637a5d0ccb70c8c53024f970ab50d699bc8dc1c61f0534", size = 412898, upload-time = "2025-09-25T21:46:08.16Z" }, - { url = "https://files.pythonhosted.org/packages/da/b5/25e97b533495a839780c89dac2b56e2b683100945ac4149645f9d95e7aee/pymatching-2.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b90cf168645adb78c9f81dd60c9e97e2aae005386b5795b54c8b4055d1492b1b", size = 626112, upload-time = "2025-09-25T21:46:09.591Z" }, - { url = "https://files.pythonhosted.org/packages/d9/50/e34731b6e7f1a46facaa5c189cc95a55b49a00730203d09f211ef4d5ee58/pymatching-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f3a0d307b30ef52b7a9cabb58ec27a08ef057e09bcbd00ffe3c40f4e31e606d", size = 347938, upload-time = "2025-09-25T21:46:10.65Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ee/1e89ba6a2811fad9f1c8d4254812c97904ee386e0490cdd0ee8c68d5d1cd/pymatching-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5c010d2dc808a58e80874ada568a4d2bb8c9e81959b858e2e56a84ccc6b2b4ac", size = 380256, upload-time = "2025-09-25T21:46:11.567Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ef/a3114a4c29cd43e180f0c494aa71beda969a347ee34459c4f816597d5e53/pymatching-2.3.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:8e3122358a398853d9e809beadcf52b2d7f38a7cac21e14bca6ce0b44045c376", size = 413004, upload-time = "2025-09-25T21:46:12.883Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/0d0494562792575be67358dcd8951678449b273af168dd5e52b887e51ab1/pymatching-2.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0edddbae8437d173d7c1314c9cb853d4abc9b84be9b2274f76972ba89b1b5b5", size = 625873, upload-time = "2025-09-25T21:46:14.068Z" }, - { url = "https://files.pythonhosted.org/packages/1f/c4/0e53ff3c9312ffb0e1677ebb62172660c5614077dca9c69ccfc31b90b7e4/pymatching-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:7ebce2e8bc98379f0bb7643a0ef0171142cf384ba626e46108c84249c868f0a6", size = 347977, upload-time = "2025-09-25T21:46:15.044Z" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/19/39472c89d532e576890320eff5e9738e4b4d7828d027315f7e198ba0be8c/pymatching-2.4.0.tar.gz", hash = "sha256:0c30dafa506b086f50d15530debd7b7b39228597d22c52d5ccd114bcbb0a2326", size = 347169, upload-time = "2026-05-22T18:10:07.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/56/f12f6ffbae1eb625cd5865e819a68c4c3a18ca66a805b922d627bdaaf6d1/pymatching-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51ee0ffa161f35ae6b887a0bebdf85e3fa456139e56d5e070823ef0434f692f", size = 380016, upload-time = "2026-05-22T18:09:21.66Z" }, + { url = "https://files.pythonhosted.org/packages/36/ce/833b2d944f89b9747dc3471a800908549df0739ce4183440bde64b9fe2df/pymatching-2.4.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:8778d0a06b606b169f8517279f01d4c0f9bf1725085f7a3d1ddcb24a53952fdd", size = 411645, upload-time = "2026-05-22T18:09:23.678Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/fb2c636ecd614617f5facf075b9b01d2bde4bdac3f84fe930b8e9db8079e/pymatching-2.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd8699227cda0d9c95152ee67ce2e5119fc845ff7951bfd8ed34dab9628733c1", size = 623826, upload-time = "2026-05-22T18:09:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e3/eb30dd8f5e0e209d0fc703fcf43a6a4c37703339198ba12a9c92b78c4393/pymatching-2.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4683b652ba3983bc26405107382d666d8ccf68f53ea50fc5fb274251871177ec", size = 345239, upload-time = "2026-05-22T18:09:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/0e/21/e1985ee29b8cc3f317c8a2504fdee1bb9ba1e13c1dd1220a9bcf35ad537c/pymatching-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:133168b1fec946bfc992a9e5abbdbbc7eebc672b04d27cfd060ea153eb6cf576", size = 381268, upload-time = "2026-05-22T18:09:28.887Z" }, + { url = "https://files.pythonhosted.org/packages/ca/21/67745b4a411f0bad057cb16c7243dceb411450bf2719e40aceaeb595d42c/pymatching-2.4.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:df8c9229c5265d71f871fba3fbdbd27cda71ca81969275eefeb64688ee2f59f4", size = 412929, upload-time = "2026-05-22T18:09:30.533Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6c/9b5a4c35727d74917b8a611f8cacbe9e5c81198648bcd7b5958f6573d39e/pymatching-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac25684a41ab859cbb0f2ea34d86485aa89d4d22f97df684f9579c167c7d560a", size = 624601, upload-time = "2026-05-22T18:09:32.111Z" }, + { url = "https://files.pythonhosted.org/packages/7c/27/990d222ce88eba0f9afa4af68dc00adaf0134348c743feee0c1ab647f9eb/pymatching-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a1b831f8ebf47f5e2a865d1bcc250da208c57ff881a37177ee548248da447c2", size = 345942, upload-time = "2026-05-22T18:09:33.822Z" }, + { url = "https://files.pythonhosted.org/packages/80/bc/4983d49570d97b4489de8ed1580faea58e63a16e6c7046e45aaa636ea054/pymatching-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:881f3410975b5a2d393561d44fa4513c878653d0e718e55930b08f52e9a04fc5", size = 380199, upload-time = "2026-05-22T18:09:35.25Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0e/00cd2b9c2da28fb3dafb77da8e2da18ceedda42de2298c682fbb016a9189/pymatching-2.4.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:af03cdd7389e7b9f93069f1dd1bb9931c3836a2bfc0d01952993f394b0bfa10d", size = 412898, upload-time = "2026-05-22T18:09:37.024Z" }, + { url = "https://files.pythonhosted.org/packages/0f/03/b3186c28500a5aa751f7fc37b52fe6129ba24092604a8118faea456f482c/pymatching-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15e6d73153713a8f383f44ba4497d478fb4c4d765fbdd30f9fc1e1d47af75760", size = 626230, upload-time = "2026-05-22T18:09:38.547Z" }, + { url = "https://files.pythonhosted.org/packages/60/c8/d9abe73cc6dbaddf61d3c5875a9687aaa9591a976b109cc7cb6b7595cccb/pymatching-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:eb4428581a2578a8cc77f695ab267c184f444d9de5cb25301fe9f3f893214ea4", size = 347707, upload-time = "2026-05-22T18:09:40.805Z" }, + { url = "https://files.pythonhosted.org/packages/ac/18/375faa94219e1781ef5fc05aac95a2ba1047e0071069f2aae1416bfeb393/pymatching-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fe3baa498155a227f5ce1ee6fe07f40524d9fb9961ac299a2050e69f7de50dba", size = 380257, upload-time = "2026-05-22T18:09:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/79/eb/36ceb0597d6ec4fc2c976b076c9523450ef61812173fc8c58bce669d6328/pymatching-2.4.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:a49db8f20d01523c9e12fc8207a5831ed261b53fb8e629c78e45b0081fcd5199", size = 413005, upload-time = "2026-05-22T18:09:44.574Z" }, + { url = "https://files.pythonhosted.org/packages/80/8d/a4450b34a032cc7eee70b45c21a03d32c89dad311ea5b41deb386539fbcb/pymatching-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6450797ca5001449fcf5b43a68181a783f4ca50650f880a02ef5bf91b13aa359", size = 625936, upload-time = "2026-05-22T18:09:46.059Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c5/ca8cd432ce388cdbdc7df86110a2987d7526743269416d0a1cc83d5b2fd7/pymatching-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a14022ecf4e810152db998d14429d430630433b9ed1edc2abe53494753b0a0ca", size = 347728, upload-time = "2026-05-22T18:09:47.678Z" }, + { url = "https://files.pythonhosted.org/packages/13/65/4289d2923789a407ca34e67b0d44f47caeba1f2219abdfa5f679a66e6e72/pymatching-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e071c90c108daf1c6f8cefcac9ad4989a1a1694fe7433ee413cef7b65f4207e", size = 381252, upload-time = "2026-05-22T18:09:49.122Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0f/5d532d693eca9bfe49e4a798a3e3d3b05f1da24b8aee7b4e21c3ade55502/pymatching-2.4.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:f2390db2679b5009509b6ea1b0838187966c2d8fd1ab8050e5d9b6c908a3ea7e", size = 413276, upload-time = "2026-05-22T18:09:50.624Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/be44c3e2f8749652c23adc77a94c4efe5383287a4c01a548cc031d0e439c/pymatching-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70869b2a4a8fab2f1e54cc0b68fd050b7556133b5ce8c2d2d5d4f4d1319c14d3", size = 625898, upload-time = "2026-05-22T18:09:52.415Z" }, + { url = "https://files.pythonhosted.org/packages/17/0d/eb2df17fe0bf089c43445ba759e89150a0d36fb7fcbc44e750d8d31f0dbe/pymatching-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:5c5ce4ed245d3670ea7d62b09f8a29b105533b032a3d21fdf6df04c9b6441ff1", size = 356304, upload-time = "2026-05-22T18:09:54.3Z" }, ] [[package]] @@ -3147,7 +3179,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -3158,9 +3190,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -3199,11 +3231,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.31" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/7e/9b35ad8f3d9ca680f7c87a88f19612fdd8da9796c4d3b46e560ac79dcc4a/python_multipart-0.0.31.tar.gz", hash = "sha256:fc631183bb13e56db3158a4909908dfb2e23565286744e798241e63750e5d680", size = 46689, upload-time = "2026-06-04T08:27:49.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/1e/7f7f299527a5a8ad90acd5f2f78dfa6c8495c6301a3205106ea68a84de96/python_multipart-0.0.31-py3-none-any.whl", hash = "sha256:8408153d68a9773291fc1da39a8b85a50044bddbabd2dd72e9229776b7b15e28", size = 29996, upload-time = "2026-06-04T08:27:47.804Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] @@ -3311,7 +3343,7 @@ wheels = [ [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -3319,9 +3351,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -3352,28 +3384,27 @@ wheels = [ [[package]] name = "s3fs" -version = "2026.4.0" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiobotocore" }, - { name = "aiohttp" }, + { name = "botocore" }, { name = "fsspec" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/d8/76f3dc1558bdf4494b117a9f7a9cc0a5d9d34edadc9e5d7ceabc5a6a7c37/s3fs-2026.4.0.tar.gz", hash = "sha256:5bdce0abb00b0435ee150807a45fea727451dbc22de4cbc116464f8504ab9d37", size = 85986, upload-time = "2026-04-29T20:52:51.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/504cb277632c4d325beabbd03bb43778f0decb9be22d9e0e6c62f44540c7/s3fs-0.4.2.tar.gz", hash = "sha256:2ca5de8dc18ad7ad350c0bd01aef0406aa5d0fff78a561f0f710f9d9858abdd0", size = 57527, upload-time = "2020-03-31T15:24:26.388Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a4/9d1ea10ebc9e028a289a72fec84da170689549a8102c8aacfcad26bc5035/s3fs-2026.4.0-py3-none-any.whl", hash = "sha256:de0d2a1f33cdf03831fd2382d278c6e4e31fe57c3bf2f703c61f8aec6b703e2a", size = 32392, upload-time = "2026-04-29T20:52:50.295Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e4/b8fc59248399d2482b39340ec9be4bb2493846ac23641b43115a7e5cd675/s3fs-0.4.2-py3-none-any.whl", hash = "sha256:91c1dfb45e5217bd441a7a560946fe865ced6225ff7eb0fb459fe6e601a95ed3", size = 19791, upload-time = "2020-03-31T15:24:24.952Z" }, ] [[package]] name = "s3transfer" -version = "0.17.0" +version = "0.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/ec/7c692cde9125b77e84b307354d4fb705f98b8ccad59a036d5957ca75bfc3/s3transfer-0.17.0.tar.gz", hash = "sha256:9edeb6d1c3c2f89d6050348548834ad8289610d886e5bf7b7207728bd43ce33a", size = 155337, upload-time = "2026-04-29T22:07:36.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/72/c6c32d2b657fa3dad1de340254e14390b1e334ce38268b7ad51abda3c8c2/s3transfer-0.17.0-py3-none-any.whl", hash = "sha256:ce3801712acf4ad3e89fb9990df97b4972e93f4b3b0004d214be5bce12814c20", size = 86811, upload-time = "2026-04-29T22:07:34.966Z" }, + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, ] [[package]] @@ -3440,19 +3471,10 @@ name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version == '3.11.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3518,6 +3540,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + [[package]] name = "secretstorage" version = "3.5.0" @@ -3542,19 +3626,18 @@ wheels = [ [[package]] name = "sinter" -version = "1.15.0" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "matplotlib" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "stim" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/ce/ba7f22ac1535b95b16f046678d133b2af65288effb6e4e99c28b576ef54d/sinter-1.15.0.tar.gz", hash = "sha256:47c7e4412d73bcbd5cabca49b6d9bfc0b33eb18d333f525076512e0efa873d45", size = 176702, upload-time = "2025-05-07T06:19:38.872Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/45/be8193757e44880348dc9561bedadce0b815b408c959ceaf56c1863fd65d/sinter-1.15.0-py3-none-any.whl", hash = "sha256:6bec05cc643e301192c2acaca9ede464576572635c5e25a592e48294b7e53bed", size = 196891, upload-time = "2025-05-07T06:19:37.398Z" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/cd/6b/b546cc9240fb1b64417c8ca00038ce1927ffd6e8a0d4e46ad237242dbcd8/sinter-1.16.0.tar.gz", hash = "sha256:fd0f7bff97cb951345893f53b347d76f99b53977590054abc3d057150f82037b", size = 177330, upload-time = "2026-05-22T05:41:49.762Z" } [[package]] name = "six" @@ -3594,28 +3677,33 @@ wheels = [ [[package]] name = "stim" -version = "1.15.0" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/15/0218eacd61cda992daf398bc36daf9830c8b430157a3ac0c06379598d24a/stim-1.15.0.tar.gz", hash = "sha256:95236006859d6754be99629d4fb44788e742e962ac8c59caad421ca088f7350e", size = 853226, upload-time = "2025-05-07T06:19:30.452Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/e8/5d0c058e59ba156c6f1bfd8569a889dec80154e95d7903bf50bea31814ec/stim-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c10d2022b3c4c245f5f421dbf01b012a4d04901df697d9aca69eaea329c8532", size = 1952385, upload-time = "2025-05-07T06:18:29.003Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/e82bd61413db51c92642620340c9175f0e1e93d2afc5274e8fa775831326/stim-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f240c196f23126bfed79bd78de5baa1fdde9c8fbfe56de032a12657fc42da37", size = 1824039, upload-time = "2025-05-07T06:18:31.537Z" }, - { url = "https://files.pythonhosted.org/packages/d8/06/b267359c50d735ca718dd487ec57842d0ed34865b62b0d8e6bdc3381d611/stim-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c55fad7529d6ee508f268534eeca1433017f2e83082f88275bea362b94f30f", size = 4982908, upload-time = "2025-05-07T06:18:33.035Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2c/84b07f2fe78f382c3514ce3863554ae47019536293d366e80e57598fe9cb/stim-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:d94638feaac9d037690779c383592bb898eda9db460d23fc0652d10030d570c9", size = 2624472, upload-time = "2025-05-07T06:18:34.678Z" }, - { url = "https://files.pythonhosted.org/packages/94/5f/82a80a3b0e494af4723737ea2109e64edbedc25fe05dcee8918e70d3a060/stim-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:48525d92965cc65b61399a9e1fe1d7a8925981bb4430ef69866d4e5c67a77d16", size = 1956537, upload-time = "2025-05-07T06:18:36.685Z" }, - { url = "https://files.pythonhosted.org/packages/a8/82/0a01580071c6d50107298e93faa88250fc30f1538117ec887ec48de7816d/stim-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0bb3757c69c9b16fd24ff7400b5cddb22017c4cae84fc4b7b73f84373cb03c00", size = 1826988, upload-time = "2025-05-07T06:18:38.598Z" }, - { url = "https://files.pythonhosted.org/packages/d7/c1/1dfa90b0622070eb39b4260eca26814d6fbac0f278e23b156072d9fac86b/stim-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0fb249f1a2897a22cbe4e0c2627abf49188cbbf19b942d4749972d1c3bdf12c", size = 4989254, upload-time = "2025-05-07T06:18:40.628Z" }, - { url = "https://files.pythonhosted.org/packages/cb/27/5b8e8155e7fb75a9313e70f77a62233e0b9041c5acb60f6cf5a908d221e8/stim-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e3b61a2d9dc4b4312f5cf2ccf9c9f7175fe13a12e5c08df99835c5275680919", size = 2625370, upload-time = "2025-05-07T06:18:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/65/99/da44f1fde8692deb74e291899699ee166e5726b975addff50f0f68bfc4c1/stim-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d426e00afe21478828369df3aaa82905e710c5b1f72582ec45244e3739d6183d", size = 1974467, upload-time = "2025-05-07T06:18:44.665Z" }, - { url = "https://files.pythonhosted.org/packages/46/f3/5aa6a7b31bcc9fb2540f65954b99dbf1e8c5fcd8d0aa164857b74e5eae9a/stim-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc613f78bc88b4318d7f34f9fddacec52638c11b72cc618f911bdd7ca153f938", size = 1838840, upload-time = "2025-05-07T06:18:46.025Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/f3b56b07c0c3fb31cb973a5c47ef88da022a859940dd46c910b706fc74aa/stim-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdd9e5ab85ba2fb113b8834422518f6e46a4aea2e0f6f7305cfc2ad0fcd07086", size = 4968123, upload-time = "2025-05-07T06:18:48.197Z" }, - { url = "https://files.pythonhosted.org/packages/81/7e/abfed103a045a6ee8c7f3f00cd820d1cf9127304066aec42ea9fb89ee9c0/stim-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:e92d5be90f6c92bada6b5aea64dfe9c80813a06e1316a71d5a36203dd24492f5", size = 2625908, upload-time = "2025-05-07T06:18:49.681Z" }, - { url = "https://files.pythonhosted.org/packages/28/7f/825d745dc128321dd2f41da75d18111121a90e7bb711da24f28b1e003c9e/stim-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:673a323402c266b1a1225565d69d31816c3d4a4c259383ed4fa9c15cacd12411", size = 1974528, upload-time = "2025-05-07T06:18:51.125Z" }, - { url = "https://files.pythonhosted.org/packages/bb/99/10604264cd7159573d6d01cdf5f9675c71580dcc3df5c533fccabad59cda/stim-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:35e36d0479015b4dcb4261b8b68be85067cbd4bac5632bdfdb3ee3f8671d05a9", size = 1838700, upload-time = "2025-05-07T06:18:52.95Z" }, - { url = "https://files.pythonhosted.org/packages/25/97/1bf3bf16129667eff1c0d0f3bb95262a2bec8c8d1227aa973b8e2a1935b6/stim-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb9465ab120837ecbd26b5af216a00715f04da087ddcfa09646892c8de720d09", size = 4967782, upload-time = "2025-05-07T06:18:54.94Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/77/bd/c0f5d6241cdb806400a63e0c7febe22982ddf1c8761142bb0ea6b51d3f50/stim-1.16.0.tar.gz", hash = "sha256:9092a996429dcf616d8d4ca01fec886b7b310caf9425a39fcfe499005636bd52", size = 882497, upload-time = "2026-05-22T05:41:56.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/c4/b2cb8ed667da891b4835826eba3cb582c577ccc6980f15fd0e84d5e6491e/stim-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d467b5599de7e171651a1ff7fac353dd64d78df75d9c63e7d870d576eb0fd64b", size = 2043460, upload-time = "2026-05-22T05:41:19.371Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/f7ebbe95361ce7b5c5da47ce024543846d6aca9fbbedd6833aeb33dccf15/stim-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84b777678cc12e808b3d6747d57e07eed951f8ec18047a936dfa25dbc950a2e2", size = 1889327, upload-time = "2026-05-22T05:41:21.869Z" }, + { url = "https://files.pythonhosted.org/packages/c5/30/90d9ed81b10c4850e108b81346c2c6e2784126945342d591272ed090e027/stim-1.16.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41c02d52af847e0ab886c35751e8f7e17e2e0d055e390ece93a50a12dcf87e56", size = 5235305, upload-time = "2026-05-22T05:41:23.6Z" }, + { url = "https://files.pythonhosted.org/packages/4c/43/4e5036fdc3c8bdfb4d774a4fff0cb8a0f44609ae18aa858ae37f61649cd1/stim-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:609666b77197d45705e1579a9840123a53cebe614e8bf1d496c0df24f797e653", size = 2753255, upload-time = "2026-05-22T05:41:25.49Z" }, + { url = "https://files.pythonhosted.org/packages/3f/1d/3619629c01407eef4042c22299afaf8d3f5fc3b0b26c98ffb4cd86f7cc1e/stim-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d44f858f3d5d1107e674886facbb011ed85438f0888278d6aa3c1f42d9e5a5f", size = 2046328, upload-time = "2026-05-22T05:41:27.345Z" }, + { url = "https://files.pythonhosted.org/packages/be/37/3742215d5fd4e090ce7ae964e43d978d1ac9865b5865530a6d5f1063a124/stim-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1890bda08adfd6daa875d1d5e41039c5ae8c58faabc7cd1813f8d8de9f1536a2", size = 1892693, upload-time = "2026-05-22T05:41:28.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d8/a84d59b49395e4f8e080991915929c69e1e6a40c411a9c0998904dbf8bef/stim-1.16.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd2d28093818f981f8dedb2be160b62d818279b81748d72dc4fca6cdd160291d", size = 5248218, upload-time = "2026-05-22T05:41:31.191Z" }, + { url = "https://files.pythonhosted.org/packages/b8/87/a845b9d17ab5e352eee30169a58bd8213e85ff47c6340daff2f8a25ed28e/stim-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:b367f90826e796417932ea46c0e53d80e3c5bebac0c6633c0aa07a8fcaa4298b", size = 2755461, upload-time = "2026-05-22T05:41:33.426Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/8b70c39860e67e233178a9ef595c15a7ec718f0b88bc424ae2c6ad8f97da/stim-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3e2883216fb4f57411531c99dfa1838c0d8201c619ccd01b197b9afd54d326cf", size = 2067905, upload-time = "2026-05-22T05:41:34.9Z" }, + { url = "https://files.pythonhosted.org/packages/51/e3/45a8c0889f4629027cbdd8ed8e9f5824b5bd8b12b0f5d24be27ec0c2a3dc/stim-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b8580dfbe5ff27594dd15a83b24262630b82cf089d1463d29dbee90ffbc6898b", size = 1902644, upload-time = "2026-05-22T05:41:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/71/db565f5bd0f273414e90d85551eed1c75f7b689847bd605a7cf0c4c0386d/stim-1.16.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883b24c3c95fa69818e931e8b60daf4fc8195d159d321e7548d8706dbac16558", size = 5307850, upload-time = "2026-05-22T05:41:37.69Z" }, + { url = "https://files.pythonhosted.org/packages/bb/65/0ddfa6f76c4a137aefeee4d51a65945e0ec77cc2221322f13a5768f4c488/stim-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:85b9c544c65377a42c6b5c9a57616a5ed5580e1cabb286a604800a045c7a12fb", size = 2760794, upload-time = "2026-05-22T05:41:39.731Z" }, + { url = "https://files.pythonhosted.org/packages/e4/cf/aeb85a75eca23907ba32c09c95340489a60ff608a9a18ebd26b9ae2e2323/stim-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1822adb4920f4616b253792fe1893ad07a9bea8cbc45b2f4cdf7c75a8083cd00", size = 2068071, upload-time = "2026-05-22T05:41:42.237Z" }, + { url = "https://files.pythonhosted.org/packages/21/40/9499fb076b819b479bffad35afd34e84a93c86f117fb5431dab211adff4f/stim-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:66dbc6532169df84d127ceede353b15445e845a94aa008c5d0a4062b51cc2b02", size = 1902498, upload-time = "2026-05-22T05:41:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/6a9bca80b6ffe0fc4a3b0375ac18ff9fb1e815d152691332e55d1a9e5fa2/stim-1.16.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22a009bad1e7816aeb70bdeafb6789e4840710da070f69cde91e2ac6cf1e800", size = 5307953, upload-time = "2026-05-22T05:41:45.32Z" }, + { url = "https://files.pythonhosted.org/packages/d7/15/db7f4b80a0903e0962f5d824ca8cb5a962836399bd45fda59961782ebb20/stim-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55231457b5897b0ef798059aae01d9e8247fc8b2089e8a27f9f4bb13b0342af2", size = 2760785, upload-time = "2026-05-22T05:41:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/4266593404f455c676bfe95785f865d77e98578fc4a35af9835653a65598/stim-1.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1e78fe10ab4c28af9ceb57ac8ed895f3efcde9607d851ef931c53d1f6f80bc8e", size = 2062900, upload-time = "2026-05-22T05:41:48.975Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/a898d8e587a8718daabaf886f2d0c22c126a691886765096a0776b5b463c/stim-1.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fa60a13ef921d6e6aea420e2fe7551d71a4a07b513d86da89e39259dd3739f9", size = 1898162, upload-time = "2026-05-22T05:41:50.973Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/8535802f6eb5230678b067cde35b81fc612c612698f192b31164c6a19b9f/stim-1.16.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f94040cbeeff786e7fb8860249e54d9ba3f777b5b098d21f1eccd072d3a3c32a", size = 5311829, upload-time = "2026-05-22T05:41:53.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/85/be0fe295a5c5426aec0a2f8497360d9db8083d62c04de81bf744d29a131e/stim-1.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:ed3aa70f82690946ba6f70abb9dad448d49ab9499fa5161de5f286da87c30ab1", size = 2825951, upload-time = "2026-05-22T05:41:55.018Z" }, ] [[package]] @@ -3700,38 +3788,38 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.3" +version = "4.68.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, ] [[package]] name = "traitlets" -version = "5.14.3" +version = "5.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, ] [[package]] name = "typer" -version = "0.25.1" +version = "0.26.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, ] [[package]] @@ -3775,25 +3863,25 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.47.0" +version = "0.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/b1/8e7077a8641086aea449e1b5752a570f1b5906c64e0a33cd6d93b63a066b/uvicorn-0.47.0.tar.gz", hash = "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533", size = 90582, upload-time = "2026-05-14T18:16:54.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/41/ac2dfdbc1f60c7af4f994c7a335cfa7040c01642b605d65f611cecc2a1e4/uvicorn-0.47.0-py3-none-any.whl", hash = "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", size = 71301, upload-time = "2026-05-14T18:16:51.762Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] [[package]] name = "wcwidth" -version = "0.6.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, ] [[package]] @@ -3814,237 +3902,127 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, ] -[[package]] -name = "wrapt" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" }, - { url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" }, - { url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" }, - { url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" }, - { url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" }, - { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" }, - { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" }, - { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" }, - { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" }, - { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" }, - { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" }, - { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" }, - { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" }, - { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, - { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, - { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, - { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, - { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, - { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, - { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, - { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, - { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, - { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, - { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, - { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, - { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, - { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, - { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, - { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, - { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, - { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, - { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, - { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, - { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, - { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, - { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, - { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, - { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, - { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, - { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, - { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, - { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, -] - [[package]] name = "yarl" -version = "1.23.0" +version = "1.24.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/0d/9cc638702f6fc3c7a3685bcc8cf2a9ed7d6206e932a49f5242658047ef51/yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107", size = 123764, upload-time = "2026-03-01T22:04:09.7Z" }, - { url = "https://files.pythonhosted.org/packages/7a/35/5a553687c5793df5429cd1db45909d4f3af7eee90014888c208d086a44f0/yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d", size = 86282, upload-time = "2026-03-01T22:04:11.892Z" }, - { url = "https://files.pythonhosted.org/packages/68/2e/c5a2234238f8ce37a8312b52801ee74117f576b1539eec8404a480434acc/yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05", size = 86053, upload-time = "2026-03-01T22:04:13.292Z" }, - { url = "https://files.pythonhosted.org/packages/74/3f/bbd8ff36fb038622797ffbaf7db314918bb4d76f1cc8a4f9ca7a55fe5195/yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d", size = 99395, upload-time = "2026-03-01T22:04:15.133Z" }, - { url = "https://files.pythonhosted.org/packages/77/04/9516bc4e269d2a3ec9c6779fcdeac51ce5b3a9b0156f06ac7152e5bba864/yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748", size = 92143, upload-time = "2026-03-01T22:04:16.829Z" }, - { url = "https://files.pythonhosted.org/packages/c7/63/88802d1f6b1cb1fc67d67a58cd0cf8a1790de4ce7946e434240f1d60ab4a/yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764", size = 107643, upload-time = "2026-03-01T22:04:18.519Z" }, - { url = "https://files.pythonhosted.org/packages/8e/db/4f9b838f4d8bdd6f0f385aed8bbf21c71ed11a0b9983305c302cbd557815/yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007", size = 108700, upload-time = "2026-03-01T22:04:20.373Z" }, - { url = "https://files.pythonhosted.org/packages/50/12/95a1d33f04a79c402664070d43b8b9f72dc18914e135b345b611b0b1f8cc/yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4", size = 102769, upload-time = "2026-03-01T22:04:23.055Z" }, - { url = "https://files.pythonhosted.org/packages/86/65/91a0285f51321369fd1a8308aa19207520c5f0587772cfc2e03fc2467e90/yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26", size = 101114, upload-time = "2026-03-01T22:04:25.031Z" }, - { url = "https://files.pythonhosted.org/packages/58/80/c7c8244fc3e5bc483dc71a09560f43b619fab29301a0f0a8f936e42865c7/yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769", size = 98883, upload-time = "2026-03-01T22:04:27.281Z" }, - { url = "https://files.pythonhosted.org/packages/86/e7/71ca9cc9ca79c0b7d491216177d1aed559d632947b8ffb0ee60f7d8b23e3/yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716", size = 94172, upload-time = "2026-03-01T22:04:28.554Z" }, - { url = "https://files.pythonhosted.org/packages/6a/3f/6c6c8a0fe29c26fb2db2e8d32195bb84ec1bfb8f1d32e7f73b787fcf349b/yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993", size = 107010, upload-time = "2026-03-01T22:04:30.385Z" }, - { url = "https://files.pythonhosted.org/packages/56/38/12730c05e5ad40a76374d440ed8b0899729a96c250516d91c620a6e38fc2/yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0", size = 100285, upload-time = "2026-03-01T22:04:31.752Z" }, - { url = "https://files.pythonhosted.org/packages/34/92/6a7be9239f2347234e027284e7a5f74b1140cc86575e7b469d13fba1ebfe/yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750", size = 108230, upload-time = "2026-03-01T22:04:33.844Z" }, - { url = "https://files.pythonhosted.org/packages/5e/81/4aebccfa9376bd98b9d8bfad20621a57d3e8cfc5b8631c1fa5f62cdd03f4/yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6", size = 103008, upload-time = "2026-03-01T22:04:35.856Z" }, - { url = "https://files.pythonhosted.org/packages/38/0f/0b4e3edcec794a86b853b0c6396c0a888d72dfce19b2d88c02ac289fb6c1/yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d", size = 83073, upload-time = "2026-03-01T22:04:38.268Z" }, - { url = "https://files.pythonhosted.org/packages/a0/71/ad95c33da18897e4c636528bbc24a1dd23fe16797de8bc4ec667b8db0ba4/yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb", size = 87328, upload-time = "2026-03-01T22:04:39.558Z" }, - { url = "https://files.pythonhosted.org/packages/e2/14/dfa369523c79bccf9c9c746b0a63eb31f65db9418ac01275f7950962e504/yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220", size = 82463, upload-time = "2026-03-01T22:04:41.454Z" }, - { url = "https://files.pythonhosted.org/packages/a2/aa/60da938b8f0997ba3a911263c40d82b6f645a67902a490b46f3355e10fae/yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99", size = 123641, upload-time = "2026-03-01T22:04:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/24/84/e237607faf4e099dbb8a4f511cfd5efcb5f75918baad200ff7380635631b/yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c", size = 86248, upload-time = "2026-03-01T22:04:44.757Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432", size = 85988, upload-time = "2026-03-01T22:04:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6c/4a90d59c572e46b270ca132aca66954f1175abd691f74c1ef4c6711828e2/yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a", size = 100566, upload-time = "2026-03-01T22:04:47.639Z" }, - { url = "https://files.pythonhosted.org/packages/49/fb/c438fb5108047e629f6282a371e6e91cf3f97ee087c4fb748a1f32ceef55/yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05", size = 92079, upload-time = "2026-03-01T22:04:48.925Z" }, - { url = "https://files.pythonhosted.org/packages/d9/13/d269aa1aed3e4f50a5a103f96327210cc5fa5dd2d50882778f13c7a14606/yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83", size = 108741, upload-time = "2026-03-01T22:04:50.838Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/115b16f22c37ea4437d323e472945bea97301c8ec6089868fa560abab590/yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c", size = 108099, upload-time = "2026-03-01T22:04:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598", size = 102678, upload-time = "2026-03-01T22:04:55.176Z" }, - { url = "https://files.pythonhosted.org/packages/85/59/cd98e556fbb2bf8fab29c1a722f67ad45c5f3447cac798ab85620d1e70af/yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b", size = 100803, upload-time = "2026-03-01T22:04:56.588Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/b39770b56d4a9f0bb5f77e2f1763cd2d75cc2f6c0131e3b4c360348fcd65/yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c", size = 100163, upload-time = "2026-03-01T22:04:58.492Z" }, - { url = "https://files.pythonhosted.org/packages/e7/64/6980f99ab00e1f0ff67cb84766c93d595b067eed07439cfccfc8fb28c1a6/yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788", size = 93859, upload-time = "2026-03-01T22:05:00.268Z" }, - { url = "https://files.pythonhosted.org/packages/38/69/912e6c5e146793e5d4b5fe39ff5b00f4d22463dfd5a162bec565ac757673/yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222", size = 108202, upload-time = "2026-03-01T22:05:02.273Z" }, - { url = "https://files.pythonhosted.org/packages/59/97/35ca6767524687ad64e5f5c31ad54bc76d585585a9fcb40f649e7e82ffed/yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb", size = 99866, upload-time = "2026-03-01T22:05:03.597Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1c/1a3387ee6d73589f6f2a220ae06f2984f6c20b40c734989b0a44f5987308/yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc", size = 107852, upload-time = "2026-03-01T22:05:04.986Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b8/35c0750fcd5a3f781058bfd954515dd4b1eab45e218cbb85cf11132215f1/yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2", size = 102919, upload-time = "2026-03-01T22:05:06.397Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1c/9a1979aec4a81896d597bcb2177827f2dbee3f5b7cc48b2d0dadb644b41d/yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5", size = 82602, upload-time = "2026-03-01T22:05:08.444Z" }, - { url = "https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46", size = 87461, upload-time = "2026-03-01T22:05:10.145Z" }, - { url = "https://files.pythonhosted.org/packages/93/95/07e3553fe6f113e6864a20bdc53a78113cda3b9ced8784ee52a52c9f80d8/yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928", size = 82336, upload-time = "2026-03-01T22:05:11.554Z" }, - { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, - { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, - { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, - { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, - { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, - { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, - { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, - { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, - { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, - { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, - { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, - { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, - { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, - { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, - { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, - { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, - { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, - { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, - { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, - { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, - { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, - { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, - { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, - { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, - { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, - { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, - { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, - { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, - { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/df/f1c7a3de0831cd83194f1a85c5bb431b13f81e6b45079314c86d1c4ef3f2/yarl-1.24.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5249a113065c2b7a958bc699759e359cd61cfc81e3069662208f48f191b7ed12", size = 129057, upload-time = "2026-05-19T21:27:47.564Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/7daafb32dd7562bf45b1ce56562e7e1a9146f6479b6456873eb8a3413c40/yarl-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4425fa244fbf530b006d0c5f79ce920114cfff5b4f5f6056e669f8e160fdc0", size = 91545, upload-time = "2026-05-19T21:27:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8f/7b3ec212f1ea0683f55f978e3246bc313c38818664edfc97a9f349a4901e/yarl-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15c0b5e49d3c44e2a0b93e6a49476c5edad0a7686b92c395765a7ea775572a75", size = 91380, upload-time = "2026-05-19T21:27:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1b/8bafab7db23b0567ae9db749099b329d91e3b82bc6028b2050ba583e116c/yarl-1.24.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:246d32a53a947c8f0189f5d699cbd4c7036de45d9359e13ba238d1239678c727", size = 105957, upload-time = "2026-05-19T21:27:53.98Z" }, + { url = "https://files.pythonhosted.org/packages/7f/77/21030c2f8d21d21559719beafc772ada2014be933418ed1eaed9cc800e42/yarl-1.24.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:64480fb3e4d4ed9ed71c48a91a477384fc342a50ca30071d2f8a88d51d9c9413", size = 97242, upload-time = "2026-05-19T21:27:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/f9ea63d1b6aa910a866e089d871fff6cbd49caab29b86b35221a62dfa0d5/yarl-1.24.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:349de4701dc3760b6e876628423a8f147ef4f5599d10aba1e10702075d424ed9", size = 114719, upload-time = "2026-05-19T21:27:58.037Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/04e0ee98ac58a249ea7ed75223f5f901ba81a834f0b4921b58e5cec11757/yarl-1.24.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d162677af8d5d3d6ebab8394b021f4d041ac107a4b705873148a77a49dc9e1b2", size = 112140, upload-time = "2026-05-19T21:27:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/ad/0b9cc9f38a7324a7eb1d80f834eaa5283d17e9271bbda3186e598dddaeac/yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5f5c6ec23a9043f2d139cc072f53dd23168d202a334b9b2fda8de4c3e890d90", size = 106721, upload-time = "2026-05-19T21:28:02.586Z" }, + { url = "https://files.pythonhosted.org/packages/65/e7/a52478ebfc66ec989e085c6ae038b9f1bfa4190baa193b133b669c709e2f/yarl-1.24.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:60de6742447fbbf697f16f070b8a443f1b5fe6ca3826fbef9fe70ecd5328e643", size = 106478, upload-time = "2026-05-19T21:28:04.523Z" }, + { url = "https://files.pythonhosted.org/packages/04/d8/5508530fea8472542de00013ae280765fc938ee196fc4030c43a498afb36/yarl-1.24.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acf93187c3710e422368eb768aee98db551ec7c85adc250207a95c16548ab7ac", size = 105423, upload-time = "2026-05-19T21:28:06.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/f1/ece28505e9628e8b756e11bb4f28864a17cc33b6b44db4d2aaf0622bf630/yarl-1.24.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4b0352fd41fd34b6651934606268816afd6914d09626f9bcbbf018edb0afb3f", size = 99878, upload-time = "2026-05-19T21:28:08.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/fb5d34529b46dd84013afcfb30b8d2bc2832ed03d412736f577d604fa393/yarl-1.24.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6b208bb939099b4b297438da4e9b25357f0b1c791888669b963e45b203ea9f36", size = 114025, upload-time = "2026-05-19T21:28:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/ff9d31aaab024f7a251c0ed308a98ae29bf9f7dc344e78f28b1322431ca2/yarl-1.24.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4b85b8825e631295ff4bc8943f7471d54c533a9360bbe15ebb38e018b555bb8a", size = 105613, upload-time = "2026-05-19T21:28:12.784Z" }, + { url = "https://files.pythonhosted.org/packages/31/7d/3296fb3f3ecd52bf9ae6c16b0895c1cda7e9170a2083861552b683f70264/yarl-1.24.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e26acf20c26cb4fefc631fdb75aca2a6b8fa8b7b5d7f204fb6a8f1e63c706f53", size = 111665, upload-time = "2026-05-19T21:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/77aa6ddaca4fbf42e45e675a465c43956dd40702281049975a2aa04eae59/yarl-1.24.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:819ca24f8eafcfb683c1bd5f44f2f488cea1274eb8944731ffd2e1f10f619342", size = 106914, upload-time = "2026-05-19T21:28:15.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/02/7611f22cd1d4ed7373eb7f9ee21fde1046edba2e7c0e514880d760352f48/yarl-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cb0f995a901c36be096ccbf4c673591c2faabbe96279598ffaec8c030f85bf4", size = 92658, upload-time = "2026-05-19T21:28:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/91/00/671d0add79938127292839ae44506ce2f7fe8909c72d5a931864f128fd0b/yarl-1.24.2-cp310-cp310-win_arm64.whl", hash = "sha256:f408eace7e22a68b467a0562e0d27d322f91fe3eaaa6f466b962c6cfaea9fa39", size = 87887, upload-time = "2026-05-19T21:28:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, ] [[package]] name = "zipp" -version = "3.23.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, ] diff --git a/prek.toml b/prek.toml index af826676e..cff8a74dd 100644 --- a/prek.toml +++ b/prek.toml @@ -70,16 +70,16 @@ pass_filenames = false types = ["python"] # License headers: hawkeye (Rust-based SPDX header checker) -# Installed via mise (see mise.toml); `mise exec --` is used so the hook -# works even outside a mise-activated shell. Run `mise exec -- hawkeye -# format` manually to apply headers to new files. +# Installed via mise in CI (see mise.toml), but called directly here so local +# installs on PATH also work. Run `hawkeye format` manually to apply headers to +# new files. [[repos]] repo = "local" [[repos.hooks]] id = "hawkeye" name = "hawkeye license header check" language = "system" -entry = "mise exec -- hawkeye check" +entry = "hawkeye check" pass_filenames = false # Python: ty type check diff --git a/skills/ppvm-usage/SKILL.md b/skills/ppvm-usage/SKILL.md index 49bc2a0f0..9a6e82678 100644 --- a/skills/ppvm-usage/SKILL.md +++ b/skills/ppvm-usage/SKILL.md @@ -1,6 +1,10 @@ --- name: ppvm-usage +<<<<<<< HEAD +description: Authoritative usage guide for ppvm, a fast quantum-circuit simulator with a Rust core and Python bindings (`ppvm-runtime`, `ppvm-tableau`, `ppvm-sym`, `ppvm-stim`, `ppvm-vihaco`, `ppvm` Python package). Use this skill whenever a task touches ppvm — importing `ppvm` in Python, depending on any `ppvm-*` crate in Rust, writing or running `.sst` programs through `ppvm-cli`, writing or modifying Pauli-propagation code, building or running circuits against the generalized stabilizer tableau, executing Stim programs, modelling depolarizing or loss noise, or even just answering "how do I do X in ppvm". Use it even when the user only hints at ppvm (mentions Pauli strings + truncation, or `GeneralizedTableau`, or "Bloqade simulation backend"). Skipping this skill is a top source of broken examples — the API has several non-obvious conventions (Heisenberg gate order, `Config`-generic types, kwargs-not-classes truncation) that look reasonable but are wrong if guessed. +======= description: Authoritative usage guide for ppvm, a fast quantum-circuit simulator with a Rust core and Python bindings (`ppvm-traits`, `ppvm-pauli-word`, `ppvm-pauli-sum`, `ppvm-tableau`, `ppvm-sym`, `ppvm-stim`, `ppvm` Python package). Use this skill whenever a task touches ppvm — importing `ppvm` in Python, depending on any `ppvm-*` crate in Rust, writing or modifying Pauli-propagation code, building or running circuits against the generalized stabilizer tableau, executing Stim programs, modelling depolarizing or loss noise, or even just answering "how do I do X in ppvm". Use it even when the user only hints at ppvm (mentions Pauli strings + truncation, or `GeneralizedTableau`, or "Bloqade simulation backend"). Skipping this skill is a top source of broken examples — the API has several non-obvious conventions (Heisenberg gate order, `Config`-generic types, kwargs-not-classes truncation) that look reasonable but are wrong if guessed. +>>>>>>> main allowed-tools: Bash, Read, Write, Edit --- @@ -355,11 +359,91 @@ What *not* to do: - Don't add a `# TODO: upstream this to ppvm` and move on. - Don't reimplement a ppvm primitive in the user's project just because the upstream version is awkward — fix the upstream awkwardness with an issue. +## Running programs from `.sst` source (`ppvm-vihaco` / `ppvm-cli`) + +`.sst` is the textual program format that `ppvm-cli` runs. A module has a +`device` header block selecting the backend and any initial state, then one +or more `fn @()` bodies. Each `circuit ` instruction pops typed +operands from the CPU stack and dispatches to the runtime. + +### Backend selection + +```sst +device circuit.n_qubits 2; +device circuit.backend paulisum; // tableau | paulisum | lossy_paulisum +device circuit.observable 1.0*ZZ+0.5*XX; // initial PauliSum state (PauliSum/Lossy only) +device circuit.coefficient_threshold 1e-6; +device circuit.max_pauli_weight 8; // PauliSum/Lossy only; defaults to no cap +``` + +- **`tableau`** (default): `GeneralizedTableau`, Schrödinger picture, measurements. +- **`paulisum`** / **`lossy_paulisum`**: `PauliSum`/`LossyPauliSum`, Heisenberg + picture. Require `circuit.observable`. `Measure`/`Reset` error at runtime. + +### Multi-term observable syntax + +`circuit.observable` accepts a Pauli-sum string: terms separated by `+`/`-`, +each term an optional coefficient (decimal or scientific) followed by an +`I/X/Y/Z` word of length exactly `n_qubits`. The `*` between coefficient and +word is optional. **No internal whitespace is allowed in the header value** +— the header parser stops at the first space — so write +`1.0*ZZ+0.5*XX-0.3*YY` rather than `1.0*ZZ + 0.5*XX - 0.3*YY`. + +### Gate-order convention + +The runtime applies `circuit ...` instructions in code order on every backend. +Whoever emits the `.sst` is responsible for emitting gates in the right +direction for the chosen picture: **forward** for Tableau (Schrödinger), +**reversed** for PauliSum/Lossy (Heisenberg). Textbook `H(0); CNOT(0,1)` on +a PauliSum target compiles to `circuit cnot; circuit h`, not the other way around. + +### `circuit trace` and `circuit truncate` + +```sst +const.str "Z?*" +circuit trace // PauliSum/Lossy: pushes state.trace(&pattern) as f64 +circuit truncate // PauliSum/Lossy: state.truncate(); Tableau: silent no-op +``` + +`trace` pops a `Value::String` (Pauli-pattern source — same grammar as +`PauliPattern::parse`), evaluates the backend-specific trace, and appends to +the machine's `trace_record` *and* pushes the value back as `Value::F64`. + +**Asymmetric semantics by backend:** +- **PauliSum / LossyPauliSum:** `state.trace(&pat)` is a filter coefficient + sum — sum of `c_P` over terms whose word matches `pat`. Use `"Z?*"` to + compute `⟨0…0|state|0…0⟩`. +- **Tableau:** `trace` returns `Σ_{P matches pat} ⟨ψ|P|ψ⟩` — the sum of + Pauli expectation values over every word the pattern enumerates. Bounded + patterns only (`Z?{n}`, positional anchors, character classes); star + quantifiers panic because they enumerate an infinite set. + +These are honest natural primitives for each backend; the same operand +will not give the same number across backends. Users shouldn't expect +agreement on a shared input. + +`truncate` takes no operand and applies the configured strategy +(`CoefficientThreshold` + `MaxPauliWeight`) to the current state. On +Tableau it's a silent no-op — gate methods already prune via +`coefficient_threshold`. **Without explicit `circuit truncate` calls in the +.sst, PauliSum runs do not truncate** — the compiler that emits the .sst +decides where to place them. + +### Running + +```bash +ppvm run program.sst --shots 100 --seed 42 +``` + +For PauliSum runs the trace values appear in the per-shot output (`run` +shows `bits | trace0,trace1,...` when both records are non-empty, just +the floats when only traces, just the bits when only measurements). + ## Where to go next - **`docs/src/pages/develop.astro`** (rendered at `/develop/`) — canonical developer guide: architecture, build/test, extending ppvm, "where to look for X" table. Read this if your task is to *modify* ppvm rather than *use* it. - **`docs/src/pages/api.astro`** (rendered at `/api/`) — full Rust + Python API reference, generated from rustdoc and griffe. -- **Examples:** `examples/trotter.rs`, `examples/symbolic.rs`, `examples/msd.rs` (Rust); `ppvm-python/docs/examples/trotter.py`, `msd.py` (Python). +- **Examples:** `examples/trotter.rs`, `examples/symbolic.rs`, `examples/msd.rs` (Rust); `ppvm-python/docs/examples/trotter.py`, `msd.py` (Python); `crates/ppvm-cli/examples/*.sst` and `crates/ppvm-vihaco/tests/*.sst` (`.sst` source). - **`AGENTS.md`** at repo root — pointer file with the agent-specific TL;DR. The repo's `Config`-trait generics are load-bearing. If you're tempted to introduce runtime dispatch on the Rust side to "simplify", that's a strong signal you should refactor the type alias and stay inside the bound instead.