diff --git a/Cargo.lock b/Cargo.lock index 7c26d8a9..e7955c32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,19 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -17,6 +30,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -26,6 +51,40 @@ dependencies = [ "libc", ] +[[package]] +name = "anndata" +version = "0.7.0" +dependencies = [ + "anyhow", + "flate2", + "indexmap", + "itertools", + "log", + "nalgebra-sparse", + "ndarray", + "num", + "parking_lot", + "paste", + "permutation", + "polars", + "rayon", + "serde", + "serde_json", + "smallvec", +] + +[[package]] +name = "anndata-zarr" +version = "0.2.0" +dependencies = [ + "anndata", + "anyhow", + "ndarray", + "serde_json", + "smallvec", + "zarrs", +] + [[package]] name = "anstream" version = "1.0.0" @@ -62,7 +121,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -73,7 +132,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -82,6 +141,52 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ar_archive_writer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object", +] + +[[package]] +name = "argminmax" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" +dependencies = [ + "half", + "num-traits", +] + +[[package]] +name = "array-init-cursor" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "assert_cmd" version = "2.2.2" @@ -97,12 +202,120 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "atoi_simd" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad17c7c205c2c28b527b9845eeb91cf1b4d008b438f98ce0e628227a822758e" +dependencies = [ + "debug_unsafe", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bit-vec" version = "0.10.1" @@ -118,6 +331,58 @@ name = "bitflags" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blosc-src" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9046dd58971db0226346fde214143d16a6eb12f535b5320d0ea94fcea420631" +dependencies = [ + "cc", + "libz-sys", + "lz4-sys", + "snappy_src", + "zstd-sys", +] + +[[package]] +name = "blusc" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4e0c17eaa785d2673fe58c22fc817946c2330ed47f3d9f79835d65950d32a45" +dependencies = [ + "flate2", + "lz4_flex", + "pkg-config", + "snap", + "zstd", +] [[package]] name = "borsh" @@ -140,9 +405,15 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "boxcar" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" + [[package]] name = "bstr" version = "1.13.0" @@ -160,6 +431,26 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -171,6 +462,9 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] [[package]] name = "caps-sa" @@ -182,6 +476,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.63" @@ -189,6 +492,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -204,17 +509,40 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" -version = "0.4.45" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", ] [[package]] @@ -248,7 +576,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -264,834 +592,3335 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "comfy-table" +version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ - "crossbeam-utils", + "crossterm", + "unicode-segmentation", + "unicode-width", ] [[package]] -name = "crossbeam-deque" -version = "0.8.6" +name = "compact_str" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.20" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] -name = "cty" -version = "0.2.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] [[package]] -name = "dashmap" -version = "6.2.1" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "core-foundation-sys", + "libc", ] [[package]] -name = "difflib" -version = "0.4.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "either" -version = "1.15.0" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] -name = "env_filter" -version = "2.0.0" +name = "cpufeatures" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "log", - "regex", + "libc", ] [[package]] -name = "env_logger" -version = "0.11.11" +name = "crc32c" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", + "rustc_version", ] [[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "libc", - "windows-sys", + "cfg-if", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] [[package]] -name = "flate2" -version = "1.1.9" +name = "crossbeam-epoch" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ - "miniz_oxide", - "zlib-rs", + "crossbeam-utils", ] [[package]] -name = "float-cmp" -version = "0.10.0" +name = "crossbeam-queue" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ - "num-traits", + "crossbeam-utils", ] [[package]] -name = "foldhash" -version = "0.1.5" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "getrandom" -version = "0.4.2" +name = "crossterm" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", + "bitflags", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", ] [[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" +name = "crossterm_winapi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ - "foldhash", + "winapi", ] [[package]] -name = "hashbrown" -version = "0.16.1" +name = "crunchy" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "heck" -version = "0.5.0" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "cty" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "dashmap" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ - "cc", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "debug_unsafe" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" [[package]] -name = "indexmap" -version = "2.13.0" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "derive_more-impl", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", + "unicode-xid", +] [[package]] -name = "itoa" -version = "1.0.18" +name = "difflib" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] -name = "jiff" -version = "0.2.28" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", + "block-buffer", + "crypto-common", ] [[package]] -name = "jiff-static" -version = "0.2.28" +name = "displaydoc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] -name = "js-sys" -version = "0.3.85" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "once_cell", - "wasm-bindgen", + "litrs", ] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "lexical-core" -version = "1.0.6" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "lexical-parse-float" -version = "1.0.6" +name = "env_filter" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ - "lexical-parse-integer", - "lexical-util", + "log", + "regex", ] [[package]] -name = "lexical-parse-integer" -version = "1.0.6" +name = "env_logger" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ - "lexical-util", + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] -name = "lexical-util" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" - -[[package]] -name = "lexical-write-float" -version = "1.0.6" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" -dependencies = [ - "lexical-util", - "lexical-write-integer", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "lexical-write-integer" -version = "1.0.6" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "lexical-util", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "libc" -version = "0.2.186" +name = "ethnum" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" [[package]] -name = "libdeflate-sys" -version = "1.25.2" +name = "event-listener" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72753e0008ea87963d2f0770042d0df7abe51fafbb8dcaf618ac440f2f1fec0a" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "cc", + "parking", + "pin-project-lite", ] [[package]] -name = "libdeflater" -version = "1.25.2" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ee41cf6fb1bb6030dfb59ffb7bc01ab26aade44142084c87f0fc7a1658fe71" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "libdeflate-sys", + "event-listener", + "pin-project-lite", ] [[package]] -name = "libmimalloc-sys" -version = "0.1.49" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" -dependencies = [ - "cc", - "cty", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "fast-float2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] -name = "lock_api" -version = "0.4.14" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "log" -version = "0.4.33" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "memchr" -version = "2.7.6" +name = "flate2" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" + +[[package]] +name = "glam" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556f6b2ea90b8d15a74e0e7bb41671c9bdf38cd9f78c284d750b9ce58a2b5be7" + +[[package]] +name = "glam" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "serde", + "zerocopy", +] + +[[package]] +name = "halfbrown" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09" +dependencies = [ + "hashbrown 0.16.1", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "rayon", + "serde", + "serde_core", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "rayon", + "serde", + "serde_core", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.2", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdeflate-sys" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72753e0008ea87963d2f0770042d0df7abe51fafbb8dcaf618ac440f2f1fec0a" +dependencies = [ + "cc", +] + +[[package]] +name = "libdeflater" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ee41cf6fb1bb6030dfb59ffb7bc01ab26aade44142084c87f0fc7a1658fe71" +dependencies = [ + "libdeflate-sys", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", + "cty", +] + +[[package]] +name = "libz-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "lz4_flex" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] name = "memmap2" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "monostate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc4dfdf312bb1b22fce127237589f4ee31652bc479f3d208332f32132aaccc9" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a656449eeb444c77364a3033f013c51869eee320c0dff805eb53024c0dfd435b" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "nalgebra" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df76ea0ff5c7e6b88689085804d6132ded0ddb9de5ca5b8aeb9eeadc0508a70a" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.10", + "glam 0.31.1", + "glam 0.32.1", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nalgebra-sparse" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df054d7815152d4e66955fc59a1f97f4036e5103134a381b6b54ec55babfa6b7" +dependencies = [ + "nalgebra", + "num-traits", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", + "serde", +] + +[[package]] +name = "noodles" +version = "0.113.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f0a067d8afc481ba06abe5bd924e89368932d664bea15d671bee3559728421" +dependencies = [ + "noodles-bam", + "noodles-bgzf", + "noodles-fastq", + "noodles-sam", +] + +[[package]] +name = "noodles-bam" +version = "0.92.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e70c87dfebe1800c6a59f1b4fc9e6d69e165876c2d475b2b684862a06225a70" +dependencies = [ + "bstr", + "indexmap", + "memchr", + "noodles-bgzf", + "noodles-core", + "noodles-csi", + "noodles-sam", +] + +[[package]] +name = "noodles-bgzf" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae8e8f7c1fd2e265d79241cea83e6f0ff7da2fa8922b5fe2f3eea8969823b60" +dependencies = [ + "bytes", + "crossbeam-channel", + "libdeflater", + "rayon", + "zlib-rs", +] + +[[package]] +name = "noodles-core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8dbac7c5f9a7de9fe45590f198a09697df631cd13d2060b4742cc48144555b0" +dependencies = [ + "bstr", +] + +[[package]] +name = "noodles-csi" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b522467eb7ae4a226b05182069e895940beda9206a3f4da9dd9395613ec9e5a8" +dependencies = [ + "bit-vec", + "bstr", + "indexmap", + "noodles-bgzf", + "noodles-core", +] + +[[package]] +name = "noodles-fastq" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bd7ca5f1595f089e7373da29fbcda325e05f02ca130424b09e9a877f9de9f9" +dependencies = [ + "bstr", + "memchr", +] + +[[package]] +name = "noodles-sam" +version = "0.87.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db484fde243408e8713278e6e83dbd67765cbd33612f2383f29b6359059d023f" +dependencies = [ + "bitflags", + "bstr", + "indexmap", + "lexical-core", + "memchr", + "noodles-bgzf", + "noodles-core", + "noodles-csi", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "now" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0" +dependencies = [ + "chrono", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + +[[package]] +name = "object_store" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622acbc9100d3c10e2ee15804b0caa40e55c933d5aa53814cd520805b7958a49" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "form_urlencoded", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools", + "parking_lot", + "percent-encoding", + "quick-xml", + "rand 0.10.2", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "permutation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df202b0b0f5b8e389955afd5f27b007b00fb948162953f1db9c70d2c7e3157d7" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "planus" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3daf8e3d4b712abe1d690838f6e29fb76b76ea19589c4afa39ec30e12f62af71" +dependencies = [ + "array-init-cursor", + "hashbrown 0.15.5", +] + +[[package]] +name = "polars" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899852b723e563dc3cbdc7ea833b14ec44e61309f55df29ba86d45cfd6bc141a" +dependencies = [ + "getrandom 0.2.17", + "getrandom 0.3.4", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-error", + "polars-io", + "polars-lazy", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-sql", + "polars-time", + "polars-utils", + "version_check", +] + +[[package]] +name = "polars-arrow" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f672743a042b72ace4f88b29f8205ab200b29c5ac976c0560899680c07d2d09" +dependencies = [ + "atoi_simd", + "bitflags", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "dyn-clone", + "either", + "ethnum", + "getrandom 0.2.17", + "getrandom 0.3.4", + "half", + "hashbrown 0.16.1", + "itoa", + "lz4", + "num-traits", + "polars-arrow-format", + "polars-buffer", + "polars-error", + "polars-schema", + "polars-utils", + "serde", + "simdutf8", + "streaming-iterator", + "strum_macros", + "version_check", + "zstd", +] + +[[package]] +name = "polars-arrow-format" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a556ac0ee744e61e167f34c1eb0013ce740e0ee6cd8c158b2ec0b518f10e6675" +dependencies = [ + "planus", + "serde", +] + +[[package]] +name = "polars-buffer" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7011424c3a79ca9c1272c7b4f5fe98695d3bed45595e37bb23c16a2978c80c" +dependencies = [ + "bytemuck", + "either", + "serde", + "version_check", +] + +[[package]] +name = "polars-compute" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a32eca8e08ac4cc5de2ac3996d2b38567bba72cdb19bbfd94c370193ed51dd" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "either", + "fast-float2", + "half", + "hashbrown 0.16.1", + "itoa", + "num-traits", + "polars-arrow", + "polars-buffer", + "polars-error", + "polars-utils", + "rand 0.9.5", + "serde", + "strength_reduce", + "strum_macros", + "version_check", + "zmij", +] + +[[package]] +name = "polars-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726296966d04268ee9679c2062af2d06c83c7a87379be471defe616b244c5029" +dependencies = [ + "bitflags", + "boxcar", + "bytemuck", + "chrono", + "chrono-tz", + "comfy-table", + "either", + "getrandom 0.3.4", + "hashbrown 0.16.1", + "indexmap", + "itoa", + "ndarray", + "num-traits", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-dtype", + "polars-error", + "polars-row", + "polars-schema", + "polars-utils", + "rand 0.9.5", + "rand_distr", + "rayon", + "regex", + "serde", + "serde_json", + "strum_macros", + "uuid", + "version_check", + "xxhash-rust", +] + +[[package]] +name = "polars-dtype" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51976dc46d42cd1e7ca252a9e3bdc90c63b0bfa7030047ebaf5250c2b7838fa6" +dependencies = [ + "boxcar", + "hashbrown 0.16.1", + "polars-arrow", + "polars-error", + "polars-utils", + "serde", + "uuid", +] + +[[package]] +name = "polars-error" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c13126f8baebc13dadf26a80dcf69a607977fc8a67b18671ad2cefc713a7bdd" +dependencies = [ + "object_store", + "parking_lot", + "polars-arrow-format", + "regex", + "signal-hook", + "simdutf8", +] + +[[package]] +name = "polars-expr" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2151f54b0ae5d6b86c3c47df0898ff90edfe774807823f742f36e44973d51ea1" +dependencies = [ + "bitflags", + "hashbrown 0.16.1", + "num-traits", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-io", + "polars-ops", + "polars-plan", + "polars-row", + "polars-time", + "polars-utils", + "rand 0.9.5", + "rayon", + "recursive", + "regex", + "version_check", +] + +[[package]] +name = "polars-io" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059724d7762d7332cbc225e6504d996091b28fa1337716e06e5a81d9e54a34ad" +dependencies = [ + "async-trait", + "atoi_simd", + "blake3", + "bytes", + "chrono", + "fast-float2", + "fs4", + "futures", + "glob", + "hashbrown 0.16.1", + "home", + "itoa", + "memchr", + "memmap2", + "num-traits", + "object_store", + "percent-encoding", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-error", + "polars-json", + "polars-parquet", + "polars-schema", + "polars-time", + "polars-utils", + "rayon", + "regex", + "reqwest", + "serde", + "serde_json", + "simdutf8", + "tokio", + "zmij", +] + +[[package]] +name = "polars-json" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55581d4cc8f4122cae92d12aec997e6713ac483871391a7db09501604007be4b" +dependencies = [ + "chrono", + "fallible-streaming-iterator", + "hashbrown 0.16.1", + "indexmap", + "itoa", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-error", + "polars-utils", + "simd-json", + "streaming-iterator", + "zmij", +] + +[[package]] +name = "polars-lazy" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e1e24d4db8c349e9576564cfff47a3f08bb831dba9168f6599be178bc725e8" +dependencies = [ + "bitflags", + "chrono", + "either", + "memchr", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-plan", + "polars-stream", + "polars-time", + "polars-utils", + "rayon", + "version_check", +] + +[[package]] +name = "polars-mem-engine" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394e4cd90186043d4051ce118e90794afbe81ac5eb9a51e358a56728e8ebde3" +dependencies = [ + "memmap2", + "polars-arrow", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "rayon", + "recursive", +] + +[[package]] +name = "polars-ops" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e47b2d9b3627662650da0a8c76ce5101ed1c61b104cb2b3663e0dc711571b12" +dependencies = [ + "argminmax", + "base64", + "bytemuck", + "chrono", + "chrono-tz", + "either", + "hashbrown 0.16.1", + "hex", + "indexmap", + "libm", + "memchr", + "num-traits", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-error", + "polars-schema", + "polars-utils", + "rayon", + "regex", + "regex-syntax", + "strum_macros", + "unicode-normalization", + "unicode-reverse", + "version_check", +] + +[[package]] +name = "polars-parquet" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436bae3e89438cafe69400e7567057d7d9820d21ac9a4f69a33b413f2666f03d" +dependencies = [ + "async-stream", + "base64", + "bytemuck", + "ethnum", + "futures", + "hashbrown 0.16.1", + "num-traits", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-error", + "polars-parquet-format", + "polars-utils", + "regex", + "serde", + "simdutf8", + "streaming-decompression", +] + +[[package]] +name = "polars-parquet-format" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-plan" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7930d5ae1d006179e65f01af57c859307b5875a4cc078dc75257250b9ae5162" +dependencies = [ + "bitflags", + "blake3", + "bytemuck", + "bytes", + "chrono", + "chrono-tz", + "either", + "futures", + "hashbrown 0.16.1", + "memmap2", + "num-traits", + "percent-encoding", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-error", + "polars-io", + "polars-ops", + "polars-time", + "polars-utils", + "rayon", + "recursive", + "regex", + "sha2", + "slotmap", + "strum_macros", + "tokio", + "version_check", +] + +[[package]] +name = "polars-row" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ea1a4554fe06442db1d6229235cd358e8eacba96aed8718f612caf3e3a646" +dependencies = [ + "bitflags", + "bytemuck", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-dtype", + "polars-error", + "polars-utils", +] + +[[package]] +name = "polars-schema" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d688e73f9156f93cb29350be144c8f1e84c1bc705f00ee7f15eb9706a7971273" +dependencies = [ + "indexmap", + "polars-error", + "polars-utils", + "serde", + "version_check", +] + +[[package]] +name = "polars-sql" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100415f86069d7e9fbf54737148fc161a7c7316a6a7d375fb6cfc7fc64f570ae" +dependencies = [ + "bitflags", + "hex", + "polars-core", + "polars-error", + "polars-lazy", + "polars-ops", + "polars-plan", + "polars-time", + "polars-utils", + "regex", + "serde", + "sqlparser", +] + +[[package]] +name = "polars-stream" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a0c054bdf16efd16bbc587e8d5418ae28464d61afd735513579cd3c338fa70" +dependencies = [ + "async-channel", + "async-trait", + "atomic-waker", + "bitflags", + "bytes", + "chrono-tz", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures", + "memchr", + "memmap2", + "num-traits", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "polars-arrow", + "polars-buffer", + "polars-compute", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-ops", + "polars-parquet", + "polars-plan", + "polars-time", + "polars-utils", + "rand 0.9.5", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e80404e1e418c997230e3b2972c3be331f45df8bdd3150fe3bef562c7a332f" +dependencies = [ + "atoi_simd", + "bytemuck", + "chrono", + "chrono-tz", + "now", + "num-traits", + "polars-arrow", + "polars-compute", + "polars-core", + "polars-error", + "polars-ops", + "polars-utils", + "rayon", + "regex", + "strum_macros", +] + +[[package]] +name = "polars-utils" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97cabf53eb8fbf6050cde3fef8f596c51cc25fd7d55fbde108d815ee6674abf" +dependencies = [ + "argminmax", + "bincode", + "bytemuck", + "bytes", + "compact_str", + "either", + "flate2", + "foldhash 0.2.0", + "half", + "hashbrown 0.16.1", + "indexmap", + "libc", + "memmap2", + "num-derive", + "num-traits", + "polars-error", + "rand 0.9.5", + "raw-cpuid", + "rayon", + "regex", + "rmp-serde", + "serde", + "serde_json", + "serde_stacker", + "slotmap", + "stacker", + "uuid", + "version_check", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "positioned-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ec4b80060f033312b99b6874025d9503d2af87aef2dd4c516e253fbfcdada7" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick_cache" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.2", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ + "cfg_aliases", "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", ] [[package]] -name = "mimalloc" -version = "0.1.52" +name = "quote" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ - "libmimalloc-sys", + "proc-macro2", ] [[package]] -name = "miniz_oxide" +name = "r-efi" +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 = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.5", +] + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rayon_iter_concurrent_limit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09ee01023de07fa073ce14c37cbe0a9e099c6b0b60a29cf4af6d04d9553fed7" +dependencies = [ + "rayon", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "adler2", - "simd-adler32", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", ] [[package]] -name = "noodles" -version = "0.113.0" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f0a067d8afc481ba06abe5bd924e89368932d664bea15d671bee3559728421" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "noodles-bam", - "noodles-bgzf", - "noodles-fastq", - "noodles-sam", + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "noodles-bam" -version = "0.92.0" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e70c87dfebe1800c6a59f1b4fc9e6d69e165876c2d475b2b684862a06225a70" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustar-aligner" +version = "0.2.0" dependencies = [ + "anndata", + "anndata-zarr", + "anyhow", + "assert_cmd", + "bitflags", "bstr", - "indexmap", - "memchr", + "byteorder", + "caps-sa", + "chrono", + "clap", + "dashmap", + "env_logger", + "flate2", + "libdeflater", + "libmimalloc-sys", + "log", + "memmap2", + "mimalloc", + "nalgebra-sparse", + "noodles", "noodles-bgzf", - "noodles-core", - "noodles-csi", - "noodles-sam", + "polars", + "predicates", + "rayon", + "rustc-hash", + "serde_json", + "shlex", + "tempfile", + "thiserror", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] -name = "noodles-bgzf" -version = "0.49.0" +name = "rustls" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae8e8f7c1fd2e265d79241cea83e6f0ff7da2fa8922b5fe2f3eea8969823b60" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ - "bytes", - "crossbeam-channel", - "libdeflater", - "rayon", - "zlib-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "noodles-core" -version = "0.20.0" +name = "rustls-native-certs" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8dbac7c5f9a7de9fe45590f198a09697df631cd13d2060b4742cc48144555b0" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "bstr", + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] -name = "noodles-csi" -version = "0.58.0" +name = "rustls-pki-types" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b522467eb7ae4a226b05182069e895940beda9206a3f4da9dd9395613ec9e5a8" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "bit-vec", - "bstr", - "indexmap", - "noodles-bgzf", - "noodles-core", + "web-time", + "zeroize", ] [[package]] -name = "noodles-fastq" -version = "0.23.0" +name = "rustls-webpki" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bd7ca5f1595f089e7373da29fbcda325e05f02ca130424b09e9a877f9de9f9" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "bstr", - "memchr", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "noodles-sam" -version = "0.87.0" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db484fde243408e8713278e6e83dbd67765cbd33612f2383f29b6359059d023f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ - "bitflags", - "bstr", - "indexmap", - "lexical-core", - "memchr", - "noodles-bgzf", - "noodles-core", - "noodles-csi", + "bytemuck", ] [[package]] -name = "normalize-line-endings" -version = "0.3.0" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] -name = "num-traits" -version = "0.2.19" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "autocfg", + "windows-sys 0.61.2", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ - "cfg-if", + "core-foundation-sys", "libc", - "redox_syscall", - "smallvec", - "windows-link", ] [[package]] -name = "portable-atomic" -version = "1.13.1" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "portable-atomic-util" -version = "0.2.5" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "portable-atomic", + "serde_core", + "serde_derive", ] [[package]] -name = "predicates" -version = "3.1.4" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", + "serde_derive", ] [[package]] -name = "predicates-core" -version = "1.0.9" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "predicates-tree" -version = "1.0.12" +name = "serde_json" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "predicates-core", - "termtree", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "serde_repr" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", - "syn", + "quote", + "syn 3.0.3", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "serde_stacker" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" dependencies = [ - "toml_edit", + "serde", + "serde_core", + "stacker", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "unicode-ident", + "form_urlencoded", + "itoa", + "ryu", + "serde", ] [[package]] -name = "quote" -version = "1.0.45" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "proc-macro2", + "cfg-if", + "cpufeatures 0.2.17", + "digest", ] [[package]] -name = "r-efi" -version = "6.0.0" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "rayon" -version = "1.12.0" +name = "signal-hook" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" dependencies = [ - "either", - "rayon-core", + "libc", + "signal-hook-registry", ] [[package]] -name = "rayon-core" -version = "1.13.0" +name = "signal-hook-registry" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "errno", + "libc", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "simba" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" dependencies = [ - "bitflags", + "approx", + "num-complex", + "num-traits", + "paste", + "wide", ] [[package]] -name = "regex" -version = "1.12.3" +name = "simd-adler32" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] -name = "regex-automata" -version = "0.4.14" +name = "simd-json" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "e32d7ab2678282d21e53374fbead7119b7eacbede73685dcaac472870a29a11c" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "ahash", + "halfbrown", + "ref-cast", + "serde", + "serde_json", + "simdutf8", + "value-trait", ] [[package]] -name = "regex-syntax" -version = "0.8.9" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "rustar-aligner" -version = "0.2.0" -dependencies = [ - "anyhow", - "assert_cmd", - "bitflags", - "bstr", - "byteorder", - "caps-sa", - "chrono", - "clap", - "dashmap", - "env_logger", - "flate2", - "libdeflater", - "libmimalloc-sys", - "log", - "memmap2", - "mimalloc", - "noodles", - "noodles-bgzf", - "predicates", - "rayon", - "rustc-hash", - "shlex", - "tempfile", - "thiserror", -] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] -name = "rustc-hash" -version = "2.1.2" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "rustix" -version = "1.1.4" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", + "version_check", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "snap" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886" [[package]] -name = "semver" -version = "1.0.28" +name = "snappy_src" +version = "0.2.5+snappy.1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "4e1432067a55bcfb1fd522d2aca6537a4fcea32bba87ea86921226d14f9bad53" +dependencies = [ + "cc", + "link-cplusplus", +] -[[package]] -name = "serde" -version = "1.0.228" +[[package]] +name = "socket2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ - "serde_core", - "serde_derive", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "sqlparser" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "505aa16b045c4c1375bf5f125cce3813d0176325bfe9ffc4a903f423de7774ff" dependencies = [ - "serde_derive", + "log", + "recursive", + "sqlparser_derive", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "sqlparser_derive" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "028e551d5e270b31b9f3ea271778d9d827148d4287a5d96167b6bb9787f5cc38" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", ] [[package]] -name = "shlex" -version = "2.0.1" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "simd-adler32" -version = "0.3.8" +name = "streaming-decompression" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "bf6cc3b19bfb128a8ad11026086e31d3ce9ad23f8ea37354b31383a187c44cf3" +dependencies = [ + "fallible-streaming-iterator", +] [[package]] -name = "smallvec" -version = "1.15.1" +name = "streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" [[package]] name = "strsim" @@ -1099,6 +3928,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -1110,6 +3957,43 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tempfile" version = "3.27.0" @@ -1117,10 +4001,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1146,7 +4030,91 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", ] [[package]] @@ -1179,24 +4147,226 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-reverse" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6f4888ebc23094adfb574fdca9fdc891826287a6397d2cd28802ffd6f20c76" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe_cell_slice" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6659959f702dcdaad77bd6e42a9409a32ceccc06943ec93c8a4306be00eb6cf1" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "value-trait" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f4b4a98dfe54bc9ed3641af7ffcb837240269627dbd5cb047d13daa39736cc" +dependencies = [ + "float-cmp", + "halfbrown", + "itoa", + "ryu", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -1206,6 +4376,31 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1237,6 +4432,20 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.108" @@ -1256,7 +4465,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1291,6 +4500,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -1303,6 +4525,67 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -1311,7 +4594,7 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.2.1", "windows-result", "windows-strings", ] @@ -1324,7 +4607,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1335,9 +4618,15 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" @@ -1350,7 +4639,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1359,7 +4648,25 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", ] [[package]] @@ -1368,9 +4675,73 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[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 = "winnow" version = "1.0.4" @@ -1410,7 +4781,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1426,7 +4797,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1468,6 +4839,314 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xxhash-rust" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zarrs" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8132307b8fc041fd21f68c7987103fb6e038b11f9838c16ec43b798f5480ccf5" +dependencies = [ + "async-lock", + "base64", + "blosc-src", + "blusc", + "bytemuck", + "bytes", + "crc32c", + "derive_more", + "flate2", + "getrandom 0.3.4", + "half", + "inventory", + "itertools", + "itoa", + "libz-sys", + "log", + "lru", + "moka", + "ndarray", + "num", + "num-complex", + "paste", + "quick_cache", + "rayon", + "rayon_iter_concurrent_limit", + "serde", + "serde_json", + "thiserror", + "thread_local", + "unsafe_cell_slice", + "uuid", + "zarrs_chunk_grid", + "zarrs_chunk_key_encoding", + "zarrs_codec", + "zarrs_data_type", + "zarrs_filesystem", + "zarrs_metadata", + "zarrs_metadata_ext", + "zarrs_plugin", + "zarrs_storage", + "zstd", +] + +[[package]] +name = "zarrs_chunk_grid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf67386fd96a0336cd3e5ab5ca6cb14e0e05aee80f1acae8c4d3cf562a8bb65" +dependencies = [ + "derive_more", + "inventory", + "itertools", + "rayon", + "thiserror", + "tinyvec", + "zarrs_metadata", + "zarrs_plugin", +] + +[[package]] +name = "zarrs_chunk_key_encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9040e7feaa92d1904d492acd0cd91b97214f1791c5b5738e6c05b2ca4145a382" +dependencies = [ + "derive_more", + "inventory", + "zarrs_metadata", + "zarrs_plugin", + "zarrs_storage", +] + +[[package]] +name = "zarrs_codec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383a129a6a0cbb2c80cdba23809e5cab85159756464b7d0f112468a495c128da" +dependencies = [ + "async-trait", + "bytemuck", + "derive_more", + "futures", + "inventory", + "itertools", + "rayon", + "thiserror", + "unsafe_cell_slice", + "zarrs_chunk_grid", + "zarrs_data_type", + "zarrs_metadata", + "zarrs_plugin", + "zarrs_storage", +] + +[[package]] +name = "zarrs_data_type" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc7c594c9363278fcd9db4c205514f009944206eb093ea7ad40b85f50009f31" +dependencies = [ + "derive_more", + "half", + "inventory", + "num", + "paste", + "serde", + "serde_json", + "thiserror", + "zarrs_metadata", + "zarrs_plugin", +] + +[[package]] +name = "zarrs_filesystem" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d9010e13dcde15100ba7eea4ca8af34521110782ed0eeb170e7ca872ac786d" +dependencies = [ + "bytes", + "derive_more", + "itertools", + "libc", + "lru", + "page_size", + "pathdiff", + "positioned-io", + "thiserror", + "walkdir", + "zarrs_storage", +] + +[[package]] +name = "zarrs_metadata" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60c4c363a8a302d7babb3c29017850a7b4e0af6ca5f9ba2946263a185b62fea" +dependencies = [ + "derive_more", + "half", + "monostate", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "zarrs_metadata_ext" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2048e07848ca99c7450518e0584929300b1b6a3cf442f18b26ffd3520814bd5b" +dependencies = [ + "derive_more", + "monostate", + "num", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "zarrs_metadata", +] + +[[package]] +name = "zarrs_plugin" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbe0ed432aee86856f70ca33be36eaf4a0dae21ab730750d9280a7ca1e95046" +dependencies = [ + "paste", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "zarrs_storage" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13f043c9bd3de69637574f7a2c4b3450f8e310d650fe5c2a78a08d13095cbf9" +dependencies = [ + "auto_impl", + "bytes", + "derive_more", + "itertools", + "thiserror", + "unsafe_cell_slice", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zlib-rs" version = "0.6.3" @@ -1479,3 +5158,31 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 8a4638f9..bafbdb4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ memmap2 = "0.9" byteorder = "1" noodles = { version = "0.113", features = ["fastq", "sam", "bam", "bgzf"] } bstr = "1" +nalgebra-sparse = { version = "0.11", optional = true } # Use the pure-Rust `zlib-rs` backend instead of flate2's default `miniz_oxide`: # ~2-3x faster inflate/deflate on the FASTQ decode + BGZF (BAM) paths, with no C # toolchain dependency. Backend is chosen at build time; API is unchanged. @@ -66,9 +67,28 @@ libmimalloc-sys = { version = "0.1.49", features = ["extended"] } # mi_option_se libdeflater = "1.25.2" noodles-bgzf = { version = "0.49", features = ["libdeflate"] } +# --- AnnData/MuData output (--soloOutputFormat Zarr) ----------------------- +# scverse/anndata-rs. Optional because the tree is heavy (polars for the +# obs/var DataFrames, zarrs for the Zarr v3 store). `--no-default-features` +# gives back the lean aligner-only build; `--soloOutputFormat` then only +# accepts `MTX`. +# +# `nalgebra-sparse` / `polars` are pinned to the versions anndata 0.7 depends +# on — the `CsrMatrix` and `DataFrame` we hand to it must be the *same* types. +anndata = { version = "0.7", optional = true } +anndata-zarr = { version = "0.2", optional = true } +polars = { version = "0.53", default-features = false, optional = true } + +[features] +default = ["anndata-out"] +# Shared plumbing for AnnData backend. +anndata-out = ["dep:anndata", "dep:nalgebra-sparse", "dep:polars", "dep:anndata-zarr"] + [dev-dependencies] assert_cmd = "2" predicates = "3" +# Reading back the hand-written Zarr group metadata in the MuData output test. +serde_json = "1" [build-dependencies] chrono = { version = "0.4", default-features = false, features = ["clock"] } @@ -86,3 +106,10 @@ opt-level = 3 lto = "fat" codegen-units = 1 strip = true + +# `obsm`/`varm` DataFrames only get their index (the `obs`/`var` names) stamped +# on by anndata-rs `ig/set_index`, which is what the solo Zarr writer relies on. +# Drop this section once that lands in a crates.io release. +[patch.crates-io] +anndata = { path = "../anndata-rs/anndata" } +anndata-zarr = { path = "../anndata-rs/anndata-zarr" } diff --git a/README.md b/README.md index d6536e63..f0a8b44c 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ resident; the 16 GB sparse index is stable at ~54 s. - Chimeric alignment detection — SE and PE, 4-tier pipeline: transcript-pair search, multi-cluster, soft-clip re-seeding, residual outer re-seeding for multi-junction fusions (`--chimSegmentMin`) - Gene-level read counting (`--quantMode GeneCounts` → `ReadsPerGene.out.tab`) - Transcriptome-coordinate SAM output (`--quantMode TranscriptomeSAM`) -- **Single-cell quantification (STARsolo)** — `--soloType CB_UMI_Simple`, `CB_UMI_Complex` (multi-segment barcodes), and `SmartSeq` (plate-based, SE + PE); features `Gene`, `GeneFull` (pre-mRNA), `SJ`, and `Velocyto` (spliced/unspliced/ambiguous); barcode correction (`--soloCBmatchWLtype` Exact/1MM/1MM_multi/…), UMI dedup (`--soloUMIdedup` 1MM_All/1MM_CR/1MM_Directional/…), multi-gene UMI filtering, multi-mapper resolution (`--soloMultiMappers` Uniform/PropUnique/EM/Rescue), cell calling (`--soloCellFilter` CellRanger2.2/TopCells/EmptyDrops_CR), gzip output, and `Summary.csv` — writes STARsolo-compatible `Solo.out//{raw,filtered}/{matrix.mtx, barcodes.tsv, features.tsv}` +- **Single-cell quantification (STARsolo)** — `--soloType CB_UMI_Simple`, `CB_UMI_Complex` (multi-segment barcodes), and `SmartSeq` (plate-based, SE + PE); features `Gene`, `GeneFull` (pre-mRNA), `SJ`, and `Velocyto` (spliced/unspliced/ambiguous); barcode correction (`--soloCBmatchWLtype` Exact/1MM/1MM_multi/…), UMI dedup (`--soloUMIdedup` 1MM_All/1MM_CR/1MM_Directional/…), multi-gene UMI filtering, multi-mapper resolution (`--soloMultiMappers` Uniform/PropUnique/EM/Rescue), cell calling (`--soloCellFilter` CellRanger2.2/TopCells/EmptyDrops_CR), gzip output, and `Summary.csv` — writes STARsolo-compatible `Solo.out//{raw,filtered}/{matrix.mtx, barcodes.tsv, features.tsv}`, or, with `--soloOutputFormat Zarr`, one MuData store (`Solo.out/matrix.zarr`) holding every feature as a layer of a `gex` modality plus a junction-indexed `sj` modality - WASP allele-specific-mapping filter (`--waspOutputMode SAMtag`, `--varVCFfile`) — vW/vA/vG tags, SE + PE - Paired-end mate-overlap merging (`--peOverlapNbasesMin`, `--peOverlapMMp`) - Coverage-track output (`--outWigType bedGraph` → `Signal.{Unique,UniqueMultiple}.str{1,2}.out.bg`) diff --git a/ROADMAP.md b/ROADMAP.md index d419f612..5fdffde4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -343,7 +343,7 @@ Single-cell quantification layered around the existing aligner: the cDNA read al **Live verification — PASS:** rustar-aligner's `Gene/raw` matrix is **byte-identical to real STARsolo's** for the CellRanger-style run, confirmed deterministically (3/3 runs). The reference STAR (2.7.10b) and a Linux build of rustar-aligner run in a consistent Linux container (`test/Dockerfile.solodiff` + `test/solo_diff_docker.sh`, via colima — no Docker Desktop). This was necessary because STAR 2.7.11b reads 0 input reads on Apple-Silicon macOS (a known STAR/macOS bug, `nextChar=-1`). 479 lib + 11 integration tests, 0 clippy warnings. -**Phase 14.4 — MVP COMPLETE** (2026-06-11): UMI deduplication + raw count-matrix output. New `src/solo/count.rs`: `UmiDedup` (`--soloUMIdedup`: Exact / NoDedup / 1MM_All [default, connected-components within Hamming-1] / 1MM_Directional / 1MM_Directional_UMItools, `dirCountAdd` 0/−1); deferred 1MM_multi CB resolution via STAR's count+quality posterior (weight = `exactCount·10^(−q/10)`, prior from `whitelist.exact_count_snapshot()`); `build_matrix` groups reads by (cell,gene), collapses UMIs, and `write_gene_matrix` writes `Solo.out/Gene/raw/{matrix.mtx, barcodes.tsv, features.tsv}` (MatrixMarket `nFeatures nBarcodes nEntries`, entries `gene+1 cell+1 count`, 1-based; CellRanger-v3 3-column features.tsv; whitelist-sorted barcodes.tsv). Wired into `align_reads` post-alignment. `--soloUMIdedup` validation in params. End-to-end test (`test_starsolo_gene_matrix`): 8 reads, one cell, two Hamming-distant UMI clouds → 2 deduped molecules → matrix `1 1 2`. **A working 10x Chromium Gene count matrix.** 475 lib + 10 integration tests, 0 clippy warnings. +**Phase 14.4 — MVP COMPLETE** (2026-06-11): UMI deduplication + raw count-matrix output. New `src/solo/count.rs`: `UmiDedup` (`--soloUMIdedup`: Exact / NoDedup / 1MM_All [default, connected-components within Hamming-1] / 1MM_Directional / 1MM_Directional_UMItools, `dirCountAdd` 0/−1); deferred 1MM_multi CB resolution via STAR's count+quality posterior (weight = `exactCount·10^(−q/10)`, prior from `whitelist.exact_count_snapshot()`); `build_matrix` groups reads by (cell,gene), collapses UMIs, and `write_matrix_market` writes `Solo.out/Gene/raw/{matrix.mtx, barcodes.tsv, features.tsv}` (MatrixMarket `nFeatures nBarcodes nEntries`, entries `gene+1 cell+1 count`, 1-based; CellRanger-v3 3-column features.tsv; whitelist-sorted barcodes.tsv). Wired into `align_reads` post-alignment. `--soloUMIdedup` validation in params. End-to-end test (`test_starsolo_gene_matrix`): 8 reads, one cell, two Hamming-distant UMI clouds → 2 deduped molecules → matrix `1 1 2`. **A working 10x Chromium Gene count matrix.** 475 lib + 10 integration tests, 0 clippy warnings. **Phase 14.5–14.11 + performance** (2026-07): completed the feature-parity set — `Summary.csv` (STARsolo-faithful, CellRanger funnel split to its own file), `--soloCellFilter` CellRanger2.2/TopCells/**EmptyDrops_CR** (Monte-Carlo ambient rescue in the `filtered/` writer), `--soloFeatures` **GeneFull/SJ/Velocyto** (spliced/unspliced/ambiguous per Sullivan 2025), `--soloMultiMappers` Uniform/PropUnique/EM/Rescue, chemistries **CB_UMI_Complex** (multi-segment) and **SmartSeq** (plate-based, SE + PE fragment counts), and a rustar-vs-STARsolo SJ + multi-mapper diff harness. Performance: pipelined solo FASTQ decode, parallelized matrix build + EmptyDrops MC, libdeflate/zlib-rs for matrix gzip + BGZF, and an **O(log n + k) segment-tree gene-overlap query** (replacing STAR's linear scan — the #1 solo hotspot, ~14% wall reduction). Sparse suffix array (`--genomeSAsparseD`, byte-identical to STAR's D=2) for a 31% smaller index. 516 tests, 0 clippy warnings. diff --git a/docs-old/phase14_starsolo.md b/docs-old/phase14_starsolo.md index 230190b1..ff948779 100644 --- a/docs-old/phase14_starsolo.md +++ b/docs-old/phase14_starsolo.md @@ -238,7 +238,7 @@ usable single-cell output. when no candidate has positive weight. - **`build_matrix`** groups reads by `(cell, gene)` into UMI→multiplicity maps (resolved multi-CB records folded in), then dedups each. -- **`write_gene_matrix`** writes `Solo.out/Gene/raw/`: +- **`write_matrix_market`** writes `Solo.out/Gene/raw/`: - `matrix.mtx` — `%%MatrixMarket matrix coordinate integer general`; dims `nFeatures nBarcodes nEntries`; entries `gene+1 cell+1 count` (1-based), iterated in cell-column order. diff --git a/src/junction/mod.rs b/src/junction/mod.rs index 13776ce4..69e4331b 100644 --- a/src/junction/mod.rs +++ b/src/junction/mod.rs @@ -7,7 +7,7 @@ /// - Junction statistics collection for SJ.out.tab output pub(crate) mod chr_start_end; pub(crate) mod gtf; -mod sj_output; +pub(crate) mod sj_output; pub mod sjdb_insert; pub use sj_output::SpliceJunctionStats; diff --git a/src/junction/sj_output.rs b/src/junction/sj_output.rs index 9fa41dd1..cd665c50 100644 --- a/src/junction/sj_output.rs +++ b/src/junction/sj_output.rs @@ -35,6 +35,15 @@ pub(crate) struct SjKey { pub motif: u8, // Encoded motif value } +/// One junction's `SJ.out.tab` count columns, snapshotted out of [`SjCounts`]. +#[derive(Debug, Clone, Copy)] +pub(crate) struct SjRowCounts { + pub annotated: bool, + pub unique: u32, + pub multi: u32, + pub max_overhang: u32, +} + /// Counts for a single junction #[derive(Debug)] pub(crate) struct SjCounts { @@ -250,75 +259,82 @@ impl SpliceJunctionStats { /// solo-feature matrix. Returns the (intron_start, intron_end) absolute-coord /// keys so the SJ recorder can be mapped to matrix rows. pub(crate) fn sj_feature_order(&self, params: &Parameters) -> Vec<(u64, u64)> { - let surviving = self.compute_surviving_junctions(params); - let mut keys: Vec<(usize, u64, u64)> = self - .junctions - .iter() - .filter(|e| surviving.contains(e.key())) - .map(|e| { - let k = e.key(); - (k.chr_idx, k.intron_start, k.intron_end) - }) - .collect(); - keys.sort_unstable(); - keys.into_iter().map(|(_, s, e)| (s, e)).collect() + self.sorted_junctions(params) + .into_iter() + .map(|(k, _)| (k.intron_start, k.intron_end)) + .collect() } - /// Write the 9-column `SJ.out.tab` lines (sorted) to `writer`; returns the - /// number written. Shared by `write_output` and the SJ feature's - /// `features.tsv`, so both stay in the same order as the SJ matrix rows. - pub(crate) fn write_sj_lines( - &self, - writer: &mut dyn std::io::Write, - genome: &Genome, - params: &Parameters, - ) -> Result { + /// The surviving junctions in `SJ.out.tab` order (chromosome, then intron + /// start/end), each with its `(annotated, unique, multi, max_overhang)` + /// counts. The single source of row order for `SJ.out.tab`, the SJ feature's + /// `features.tsv`/`var`, and the SJ matrix rows. + pub(crate) fn sorted_junctions(&self, params: &Parameters) -> Vec<(SjKey, SjRowCounts)> { let surviving = self.compute_surviving_junctions(params); - let mut output_junctions: Vec<_> = self + let mut rows: Vec<(SjKey, SjRowCounts)> = self .junctions .iter() .filter(|entry| surviving.contains(entry.key())) .map(|entry| { - let key = entry.key().clone(); let counts = entry.value(); ( - key, - counts.annotated, - counts.unique_count.load(Ordering::Relaxed), - counts.multi_count.load(Ordering::Relaxed), - counts.max_overhang.load(Ordering::Relaxed), + entry.key().clone(), + SjRowCounts { + annotated: counts.annotated, + unique: counts.unique_count.load(Ordering::Relaxed), + multi: counts.multi_count.load(Ordering::Relaxed), + max_overhang: counts.max_overhang.load(Ordering::Relaxed), + }, ) }) .collect(); - - output_junctions.sort_by(|a, b| { + rows.sort_by(|a, b| { a.0.chr_idx .cmp(&b.0.chr_idx) .then(a.0.intron_start.cmp(&b.0.intron_start)) .then(a.0.intron_end.cmp(&b.0.intron_end)) }); + rows + } + + /// `(chromosome name, 1-based intron start, 1-based intron end)` for a junction. + pub(crate) fn locus<'g>(key: &SjKey, genome: &'g Genome) -> Result<(&'g str, u64, u64), Error> { + let chr_name = genome + .chr_name + .get(key.chr_idx) + .ok_or_else(|| Error::Index("Invalid chromosome index in junction".to_string()))?; + let chr_start_pos = genome.chr_start[key.chr_idx]; + Ok(( + chr_name, + key.intron_start - chr_start_pos + 1, + key.intron_end - chr_start_pos + 1, + )) + } + /// Write the 9-column `SJ.out.tab` lines (sorted) to `writer`; returns the + /// number written. Shared by `write_output` and the SJ feature's + /// `features.tsv`, so both stay in the same order as the SJ matrix rows. + pub(crate) fn write_sj_lines( + &self, + writer: &mut dyn std::io::Write, + genome: &Genome, + params: &Parameters, + ) -> Result { let mut written = 0u32; - for (key, annotated, unique, multi, max_overhang) in &output_junctions { - let chr_name = genome - .chr_name - .get(key.chr_idx) - .ok_or_else(|| Error::Index("Invalid chromosome index in junction".to_string()))?; - let chr_start_pos = genome.chr_start[key.chr_idx]; - let chr_pos_start = key.intron_start - chr_start_pos + 1; - let chr_pos_end = key.intron_end - chr_start_pos + 1; + for (key, c) in self.sorted_junctions(params) { + let (chr_name, start, end) = Self::locus(&key, genome)?; writeln!( writer, "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", chr_name, - chr_pos_start, - chr_pos_end, + start, + end, key.strand, key.motif, - i32::from(*annotated), - unique, - multi, - max_overhang + i32::from(c.annotated), + c.unique, + c.multi, + c.max_overhang ) .map_err(|e| Error::Index(format!("SJ write: {e}")))?; written += 1; diff --git a/src/lib.rs b/src/lib.rs index 6fce173a..b53dd992 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -437,7 +437,29 @@ fn write_solo_output( recorder.n_multi_records(), ); } - crate::solo::write_gene_matrix(sctx, params, stats, Some(&**sj_stats), &index.genome)?; + // Validated in `Parameters::validate`, so an unknown/unavailable format + // never reaches here. + match crate::solo::OutputFormat::parse(¶ms.solo_output_format) { + #[cfg(feature = "anndata-out")] + Some(crate::solo::OutputFormat::Zarr) => { + crate::solo::adata::write_mudata( + sctx, + params, + stats, + Some(&**sj_stats), + &index.genome, + )?; + } + _ => { + crate::solo::write_matrix_market( + sctx, + params, + stats, + Some(&**sj_stats), + &index.genome, + )?; + } + } Ok(()) } diff --git a/src/params/mod.rs b/src/params/mod.rs index 0536a85d..635026cd 100644 --- a/src/params/mod.rs +++ b/src/params/mod.rs @@ -1133,6 +1133,16 @@ pub struct Parameters { #[arg(long = "soloOutGzip", default_value = "no")] pub solo_out_gzip: String, + /// Container format for the solo count matrices (rustar extension beyond + /// STARsolo). `MTX` (default) is STARsolo's `raw/`+`filtered/` MatrixMarket + /// triplet. `Zarr` writes one sharded Zarr v3 MuData store, + /// `/.zarr`, holding every feature as a + /// layer of a `gex` modality plus a junction-indexed `sj` modality — cells × + /// features (AnnData's obs × var orientation, the transpose of `.mtx`). + /// One format per run: the two writers each consume the count records. + #[arg(long = "soloOutputFormat", default_value = "MTX")] + pub solo_output_format: String, + /// Velocyto ambiguous-molecule handling (rustar extension beyond STARsolo). /// `yes` (default) writes the three `spliced`/`unspliced`/`ambiguous` matrices /// like STARsolo — exon-only molecules with no junction/intron evidence stay in @@ -1642,6 +1652,26 @@ impl Parameters { )); } } + // soloOutputFormat value (and whether this build has the backend). + let f = ¶ms.solo_output_format; + match crate::solo::OutputFormat::parse(f) { + Some(fmt) if fmt.is_available() => {} + Some(fmt) => { + return Err(command.error( + ErrorKind::InvalidValue, + format!( + "--soloOutputFormat {f} needs the `{}` cargo feature, which this binary was built without", + fmt.cargo_feature(), + ), + )); + } + None => { + return Err(command.error( + ErrorKind::InvalidValue, + format!("unsupported --soloOutputFormat '{f}'; expected MTX or Zarr"), + )); + } + } // Gene-level features need a gene model (SJ does not — junctions come // from the alignments). let needs_gtf = params diff --git a/src/solo/adata.rs b/src/solo/adata.rs new file mode 100644 index 00000000..d3d3a4fe --- /dev/null +++ b/src/solo/adata.rs @@ -0,0 +1,565 @@ +//! `--soloOutputFormat Zarr`: the whole solo run as one MuData store (Phase 14.x, +//! a rustar extension beyond STARsolo, which only writes MatrixMarket). +//! +//! On-disk layout (Zarr v3; `mod/` groups are plain AnnData stores, the +//! MuData scaffolding around them is written here by hand — there is no MuData +//! crate): +//! +//! ```text +//! Solo.out/matrix.zarr/ encoding-type = MuData +//! uns/summary//… the Summary.csv statistics, unformatted +//! mod/ mod-order = [gex, sj] +//! gex/ cells × genes, no X +//! layers/{Gene,GeneFull} one per --soloFeatures gene feature +//! layers/{spliced,unspliced,ambiguous} +//! layers/Gene_UniqueAndMult-EM … +//! obs (whitelist barcodes), var (gene_id + gene_name) +//! obsm/stats_ per-barcode reads/UMIs/genes + is_cell +//! sj/ cells × junctions (X only) +//! ``` +//! +//! Three things are deliberate. **`obs` is the full whitelist**, identical in every +//! matrix and both modalities — so the modalities can share one MuData `obs` and +//! no cell-calling decision is baked into the axis. **Cell calling lives in each +//! output type's `obsm` frame** as an `is_cell` column, rather than one +//! `is_cell_` column per feature sprayed across `obs`; the MTX writer's +//! `filtered/` directory is that same boolean, materialized. And **`gex` has no +//! `X`**: no gene feature is privileged as *the* matrix, so every one of them is +//! a named layer and the caller picks (`sj` has a single matrix, so it keeps `X`). +//! +//! Unlike the MatrixMarket writer, which streams each feature through a temp +//! file, `set_layers` takes materialized arrays — so every layer is held in +//! memory at once. If that becomes the ceiling on a big run, the fix is to add +//! the layers one at a time through `AxisArraysOp::add` and drop each matrix +//! after it is written. + +/// Container format for the solo count matrices (`--soloOutputFormat`). +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum OutputFormat { + /// STARsolo's `raw/` + `filtered/` MatrixMarket triplet. + Mtx, + /// One sharded Zarr v3 MuData store. + Zarr, +} + +impl OutputFormat { + pub fn parse(s: &str) -> Option { + match s { + "MTX" => Some(Self::Mtx), + "Zarr" => Some(Self::Zarr), + _ => None, + } + } + + /// False when this binary was built without the backend's cargo feature. + pub fn is_available(self) -> bool { + match self { + Self::Mtx => true, + Self::Zarr => cfg!(feature = "anndata-out"), + } + } + + pub fn cargo_feature(self) -> &'static str { + match self { + Self::Mtx => "", + Self::Zarr => "anndata-out", + } + } +} + +#[cfg(feature = "anndata-out")] +pub use zarr::write_mudata; + +#[cfg(feature = "anndata-out")] +mod zarr { + use crate::error::Error; + use crate::solo::SoloContext; + use crate::solo::count::{ + CellCounts, CellStat, FeatureSummary, MultiMethod, cells_to_csr, multi_matrices, + }; + use crate::solo::whitelist::CbWhitelist; + use anndata::backend::{AttributeOp, Value}; + use anndata::data::{DataFrameIndex, Mapping}; + use anndata::{ + AnnData, AnnDataOp, AxisArraysOp, Backend, backend::GroupOp, data::ArrayData, data::Data, + }; + use anndata_zarr::Zarr; + use polars::prelude::{Column, DataFrame}; + use rustc_hash::FxHashMap as HashMap; + use std::path::Path; + + /// mudata's on-disk format versions (`mudata.__mudataversion__` / + /// `__anndataversion__`), which its reader checks the groups against. + const MUDATA_VERSION: &str = "0.1.0"; + const ANNDATA_VERSION: &str = "0.1.0"; + + /// Write the solo count matrices as one MuData store. No-op (with a warning) + /// when there is no explicit whitelist, matching the MatrixMarket writer. + pub fn write_mudata( + ctx: &SoloContext, + params: &crate::params::Parameters, + align_stats: &crate::stats::AlignmentStats, + sj_stats: Option<&crate::junction::SpliceJunctionStats>, + genome: &crate::genome::Genome, + ) -> Result<(), Error> { + let CbWhitelist::List { sorted, .. } = &ctx.whitelist else { + log::warn!( + "STARsolo: --soloCBwhitelist None matrix output is not yet supported (Phase 14.4); skipping matrix" + ); + return Ok(()); + }; + let n_obs = sorted.len(); + let n_genes = ctx.gene_ann.gene_ids.len(); + let opts = crate::solo::count::CountOptions::from_params(params); + let multi_methods = MultiMethod::parse_list(¶ms.solo_multi_mappers); + let funnel = crate::solo::count::MappingFunnel::collect(ctx, align_stats); + + let solo_dir = params + .solo_out_file_names + .first() + .cloned() + .unwrap_or_else(|| "Solo.out/".to_string()); + let stem = params + .solo_out_file_names + .get(3) + .map_or("matrix", |m| { + Path::new(m) + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("matrix") + }) + .to_string(); + let path = params.output_path(&format!("{solo_dir}{stem}.zarr")); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|e| Error::io(e, parent))?; + } + // The MuData root. `Zarr::new` wipes any previous store at this path. + let store = Zarr::new(&path).map_err(zarr_err)?; + std::fs::create_dir_all(path.join("mod")).map_err(|e| Error::io(e, &path))?; + + // The one `obs` axis every matrix and both modalities are written against. + let obs_index = DataFrameIndex::from( + (0..n_obs) + .map(|i| { + let mut buf = Vec::with_capacity(ctx.whitelist.barcode_len()); + ctx.whitelist.unpack_barcode_into(i as u32, &mut buf); + String::from_utf8_lossy(&buf).into_owned() + }) + .collect::>(), + ); + + // -- gex: every gene-indexed output type shares one cells × genes axis -- + // One (layer, obsm frame, uns summary) per output type, unzipped into the + // three collections the AnnData setters take. + let per_feature = ctx + .features + .iter() + .zip(&ctx.recorders) + .map(|(feature, recorder)| { + let name = feature.dir_name().to_string(); + let cells = crate::solo::count::dedup_cells(ctx, recorder, &opts); + let stats: Vec = cells + .iter() + .map(CellCounts::stat) + .filter(|s| s.n_umis > 0) + .collect(); + let matrix = cells_to_csr(&cells, n_obs, n_genes)?; + let called = call_cells(&stats, &matrix, n_genes, params)?; + + // UniqueAndMult- variants share the layer axis, so they + // ride along as extra layers of this feature. + let mg = recorder.multi_gene.lock().unwrap(); + let mut layers: Vec<(String, ArrayData)> = Vec::new(); + for m in multi_matrices(&matrix, &mg, &multi_methods) { + let (method, mat) = m?; + layers.push(( + format!("{name}_UniqueAndMult-{}", method.name()), + mat.into(), + )); + } + drop(mg); + + let summary = crate::solo::count::feature_summary( + &stats, + detected(&matrix, n_genes), + &funnel, + crate::solo::count::feature_reads(ctx, *feature), + ); + // The feature's own counts lead, ahead of its multimapper variants. + layers.insert(0, (name.clone(), matrix.into())); + Ok(( + layers, + ( + ( + format!("stats_{name}"), + stats_frame(&stats, &called, n_obs)?, + ), + (name, summary_map(&summary)), + ), + )) + }) + .collect::, Error>>()?; + let (layer_groups, (obsm, summaries)): (Vec<_>, (Vec<_>, Vec<_>)) = + per_feature.into_iter().unzip(); + let mut layers: Vec<(String, ArrayData)> = layer_groups.into_iter().flatten().collect(); + + // Velocyto: three more layers on the same gene axis, no separate stats. + if ctx.velocyto_enabled { + let keep_ambiguous = !matches!( + params.solo_velocyto_ambiguous.as_str(), + "no" | "No" | "false" + ); + let cells = crate::solo::count::velocyto_cells( + &ctx.velocyto_records.lock().unwrap(), + opts.method, + opts.umi_len, + keep_ambiguous, + ); + let names = ["spliced", "unspliced", "ambiguous"]; + for (k, name) in names + .iter() + .enumerate() + .take(if keep_ambiguous { 3 } else { 2 }) + { + let per_cat: Vec = cells.iter().map(|c| c[k].clone()).collect(); + layers.push(( + (*name).to_string(), + cells_to_csr(&per_cat, n_obs, n_genes)?.into(), + )); + } + } + + let mut modalities: Vec<&str> = Vec::new(); + if layers.is_empty() { + log::warn!( + "STARsolo: no gene-indexed features to write to {}", + path.display() + ); + } else { + // All layers are output as-is with keys in Layers. + let gex = AnnData::::new(path.join("mod").join("gex")).map_err(zarr_err)?; + gex.set_var(gene_frame(&ctx.gene_ann.gene_names)?) + .map_err(zarr_err)?; + gex.set_var_names(DataFrameIndex::from(ctx.gene_ann.gene_ids.clone())) + .map_err(zarr_err)?; + gex.set_obs_names(obs_index.clone()).map_err(zarr_err)?; + gex.set_layers(layers).map_err(zarr_err)?; + for (name, df) in obsm { + gex.obsm().add(&name, df).map_err(zarr_err)?; + } + gex.close().map_err(zarr_err)?; + modalities.push("gex"); + log::info!( + "STARsolo: wrote {}/mod/gex ({n_obs} barcodes × {n_genes} genes)", + path.display(), + ); + } + + // -- sj: junction-indexed, so its own modality (it cannot share `var`) -- + if ctx.sj_enabled + && let Some(sjs) = sj_stats + { + let junctions = sjs.sorted_junctions(params); + let row: HashMap<(u64, u64), u32> = junctions + .iter() + .enumerate() + .map(|(i, (k, _))| ((k.intron_start, k.intron_end), i as u32)) + .collect(); + let cells = crate::solo::count::sj_cells( + &ctx.sj_records.lock().unwrap(), + &row, + opts.method, + opts.umi_len, + ); + let stats: Vec = cells + .iter() + .map(CellCounts::stat) + .filter(|s| s.n_umis > 0) + .collect(); + let matrix = cells_to_csr(&cells, n_obs, junctions.len())?; + let called = call_cells(&stats, &matrix, junctions.len(), params)?; + + let sj = AnnData::::new(path.join("mod").join("sj")).map_err(zarr_err)?; + let (index, var) = junction_frame(&junctions, genome)?; + sj.set_var(var).map_err(zarr_err)?; + sj.set_var_names(DataFrameIndex::from(index)) + .map_err(zarr_err)?; + sj.set_obs_names(obs_index.clone()).map_err(zarr_err)?; + sj.set_x(matrix).map_err(zarr_err)?; + sj.obsm() + .add("stats_SJ", stats_frame(&stats, &called, n_obs)?) + .map_err(zarr_err)?; + sj.close().map_err(zarr_err)?; + modalities.push("sj"); + log::info!( + "STARsolo: wrote {}/mod/sj ({n_obs} barcodes × {} junctions)", + path.display(), + junctions.len(), + ); + } + + // -- the MuData scaffolding: root uns, group attributes, mod-order -- + let mut uns: HashMap = HashMap::default(); + uns.insert( + "summary".to_string(), + Mapping::from( + summaries + .into_iter() + .map(|(k, v)| (k, Data::from(v))) + .collect::>(), + ) + .into(), + ); + if let Some(cr) = funnel.cellranger_summary() { + uns.insert("cellranger_summary".to_string(), cellranger_map(&cr).into()); + } + uns.insert("run_info".to_string(), run_info().into()); + anndata::data::Writable::write( + &Mapping::from(uns.into_iter().collect::>()), + &store, + "uns", + ) + .map_err(zarr_err)?; + + for m in &modalities { + let mut g = store.open_group(&format!("mod/{m}")).map_err(zarr_err)?; + set_attrs(&mut g, "anndata", ANNDATA_VERSION)?; + } + let mut mod_group = store.new_group("mod").map_err(zarr_err)?; + mod_group + .new_json_attr( + "mod-order", + &Value::Array(modalities.iter().map(|m| Value::from(*m)).collect()), + ) + .map_err(zarr_err)?; + let mut root = store.new_group("/").map_err(zarr_err)?; + set_attrs(&mut root, "MuData", MUDATA_VERSION)?; + // axis = 0: the modalities share `obs` (the whitelist) and concatenate `var`. + root.new_json_attr("axis", &Value::from(0)) + .map_err(zarr_err)?; + Ok(()) + } + + fn set_attrs( + group: &mut ::Group, + encoding: &str, + version: &str, + ) -> Result<(), Error> { + group + .new_json_attr("encoding-type", &Value::from(encoding)) + .map_err(zarr_err)?; + group + .new_json_attr("encoding-version", &Value::from(version)) + .map_err(zarr_err)?; + group + .new_json_attr("encoder", &Value::from("rustar-aligner")) + .map_err(zarr_err)?; + group + .new_json_attr("encoder-version", &Value::from(env!("CARGO_PKG_VERSION"))) + .map_err(zarr_err) + } + + /// The whitelist indices `--soloCellFilter` calls cells for this matrix — + /// the `filtered/` directory of the MatrixMarket writer, as a list. Empty + /// for `--soloCellFilter None`. + fn call_cells( + stats: &[CellStat], + matrix: &nalgebra_sparse::CsrMatrix, + n_features: usize, + params: &crate::params::Parameters, + ) -> Result, Error> { + if params + .solo_cell_filter + .first() + .is_some_and(|m| m == "EmptyDrops_CR") + { + // Rows are whitelist barcodes, so the row index *is* the barcode. + let triplets = matrix + .triplet_iter() + .map(|(cb, gene, &v)| Ok((gene as u32, cb as u32, v as u32))); + crate::solo::count::emptydrops_called( + stats, + triplets, + n_features, + ¶ms.solo_cell_filter, + ) + } else { + Ok( + crate::solo::count::called_cells(stats, ¶ms.solo_cell_filter) + .unwrap_or_default(), + ) + } + } + + /// Genes with a nonzero count anywhere in the matrix. + fn detected(matrix: &nalgebra_sparse::CsrMatrix, n_cols: usize) -> u32 { + let mut seen = vec![false; n_cols]; + for &c in matrix.col_indices() { + seen[c] = true; + } + seen.iter().filter(|&&s| s).count() as u32 + } + + /// One output type's per-barcode statistics, full whitelist height: the + /// counting stats plus this output type's own cell call. + fn stats_frame(stats: &[CellStat], called: &[u32], n_obs: usize) -> Result { + let mut n_reads = vec![0u64; n_obs]; + let mut n_umis = vec![0u64; n_obs]; + let mut n_features = vec![0u32; n_obs]; + let mut is_cell = vec![false; n_obs]; + for s in stats { + n_reads[s.cb as usize] = s.n_reads; + n_umis[s.cb as usize] = s.n_umis; + n_features[s.cb as usize] = s.n_genes; + } + for &cb in called { + is_cell[cb as usize] = true; + } + DataFrame::new( + n_obs, + vec![ + Column::new("n_reads".into(), n_reads), + Column::new("n_umis".into(), n_umis), + Column::new("n_features".into(), n_features), + Column::new("is_cell".into(), is_cell), + ], + ) + .map_err(|e| Error::Parameter(format!("building solo obsm frame: {e}"))) + } + + /// `var` for the gene modality (the index is set separately from `gene_ids`). + fn gene_frame(gene_names: &[String]) -> Result { + DataFrame::new( + gene_names.len(), + vec![Column::new("gene_name".into(), gene_names)], + ) + .map_err(|e| Error::Parameter(format!("building solo var frame: {e}"))) + } + + /// `(var_names, var)` for the SJ modality: `chr:start-end:strand` ids plus the + /// per-junction `SJ.out.tab` columns, which STARsolo gives us for free here. + fn junction_frame( + junctions: &[( + crate::junction::sj_output::SjKey, + crate::junction::sj_output::SjRowCounts, + )], + genome: &crate::genome::Genome, + ) -> Result<(Vec, DataFrame), Error> { + let strand_char = |s: u8| match s { + 1 => '+', + 2 => '-', + _ => '.', + }; + let mut index = Vec::with_capacity(junctions.len()); + let mut motif = Vec::with_capacity(junctions.len()); + let mut annotated = Vec::with_capacity(junctions.len()); + let mut unique = Vec::with_capacity(junctions.len()); + let mut multi = Vec::with_capacity(junctions.len()); + let mut overhang = Vec::with_capacity(junctions.len()); + for (key, counts) in junctions { + let (chr, start, end) = crate::junction::SpliceJunctionStats::locus(key, genome)?; + index.push(format!("{chr}:{start}-{end}:{}", strand_char(key.strand))); + motif.push(key.motif); + annotated.push(counts.annotated); + unique.push(counts.unique); + multi.push(counts.multi); + overhang.push(counts.max_overhang); + } + let var = DataFrame::new( + junctions.len(), + vec![ + Column::new("motif".into(), motif), + Column::new("annotated".into(), annotated), + Column::new("n_reads_unique".into(), unique), + Column::new("n_reads_multi".into(), multi), + Column::new("max_overhang".into(), overhang), + ], + ) + .map_err(|e| Error::Parameter(format!("building solo SJ var frame: {e}")))?; + Ok((index, var)) + } + + /// The `Summary.csv` statistics as an `uns` mapping — the same numbers, but + /// unformatted (fractions stay fractions). + fn summary_map(s: &FeatureSummary) -> Mapping { + map([ + ("n_reads", Data::from(s.n_reads)), + ("frac_valid_barcodes", s.frac_valid_barcodes.into()), + ("saturation", s.saturation.into()), + ( + "frac_mapped_genome_unique_multi", + s.frac_mapped_genome_unique_multi.into(), + ), + ( + "frac_mapped_genome_unique", + s.frac_mapped_genome_unique.into(), + ), + ( + "frac_mapped_feature_unique", + s.frac_mapped_feature_unique.into(), + ), + ("n_cells", (s.n_cells as u64).into()), + ("reads_in_cells", s.reads_in_cells.into()), + ("frac_reads_in_cells", s.frac_reads_in_cells.into()), + ("mean_reads_per_cell", s.mean_reads_per_cell.into()), + ("median_reads_per_cell", s.median_reads_per_cell.into()), + ("umis_in_cells", s.umis_in_cells.into()), + ("mean_umis_per_cell", s.mean_umis_per_cell.into()), + ("median_umis_per_cell", s.median_umis_per_cell.into()), + ("mean_features_per_cell", s.mean_features_per_cell.into()), + ( + "median_features_per_cell", + s.median_features_per_cell.into(), + ), + ("features_detected", u64::from(s.features_detected).into()), + ]) + } + + fn cellranger_map(s: &crate::solo::count::CellRangerSummary) -> Mapping { + map([ + ("n_reads", Data::from(s.n_reads)), + ( + "frac_mapped_genome_unique", + s.frac_mapped_genome_unique.into(), + ), + ("frac_exonic", s.frac_exonic.into()), + ("frac_intronic", s.frac_intronic.into()), + ("frac_intergenic", s.frac_intergenic.into()), + ("frac_antisense", s.frac_antisense.into()), + ]) + } + + /// What produced the store: version + the exact command line. + fn run_info() -> Mapping { + map([ + ("aligner", Data::from("rustar-aligner".to_string())), + ("version", env!("CARGO_PKG_VERSION").to_string().into()), + ( + "command_line", + shlex::try_join( + std::env::args() + .collect::>() + .iter() + .map(String::as_str), + ) + .unwrap_or_default() + .into(), + ), + ]) + } + + fn map(entries: [(&str, Data); N]) -> Mapping { + Mapping::from( + entries + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect::>(), + ) + } + + /// anndata/zarrs report `anyhow::Error`; the solo writers report [`Error`]. + #[allow(clippy::needless_pass_by_value)] // used as `.map_err(zarr_err)` + fn zarr_err(e: anyhow::Error) -> Error { + Error::Parameter(format!("AnnData output: {e:#}")) + } +} diff --git a/src/solo/count.rs b/src/solo/count.rs index 9af3edf2..98c513d5 100644 --- a/src/solo/count.rs +++ b/src/solo/count.rs @@ -12,6 +12,8 @@ use crate::error::Error; use crate::solo::whitelist::CbWhitelist; use crate::solo::{SoloContext, SoloCountRecord}; +#[cfg(feature = "anndata-out")] +use nalgebra_sparse::CsrMatrix; // FxHash (non-cryptographic) rather than std's SipHash: every map here is keyed // on packed integers (u64 UMIs, u32 gene/cell ids) on the per-cell UMI-dedup hot // path, where SipHash is ~3-5x slower and buys nothing. Output is unchanged — @@ -328,18 +330,157 @@ pub struct MatrixStats { pub genes_detected: u32, } -/// Stream the per-cell deduplicated counts into a plain temporary MatrixMarket -/// *body* (`gene+1 cb+1 count`, barcode-ascending) and collect per-cell stats. -/// The body is finalized into `raw/` (and optionally `filtered/`) by the caller, -/// which lets the raw + filtered matrices share one streaming pass. -#[allow(clippy::too_many_arguments)] -fn build_matrix_body( +/// One cell's deduplicated counts: the whitelist barcode index, the reads that +/// went in (before UMI collapse), and the gene-ascending `(feature, count)` +/// entries that came out. +#[derive(Clone)] +pub struct CellCounts { + pub cb: u32, + pub n_reads: u64, + pub entries: Vec<(u32, u64)>, +} + +impl CellCounts { + pub(crate) fn stat(&self) -> CellStat { + CellStat { + cb: self.cb, + n_reads: self.n_reads, + n_umis: self.entries.iter().map(|&(_, c)| c).sum(), + n_genes: self.entries.len() as u32, + } + } +} + +/// The `--soloUMI*` knobs that drive UMI collapse, resolved once per run. +#[derive(Clone, Copy)] +pub(crate) struct CountOptions { + pub method: UmiDedup, + pub filtering: UmiFiltering, + pub umi_len: usize, + /// `*_pseudocounts` CB-match types add 1 to the 1MM_multi posterior prior. + pub pseudocount: f64, +} + +impl CountOptions { + pub(crate) fn from_params(params: &crate::params::Parameters) -> Self { + Self { + method: params + .solo_umi_dedup + .first() + .map_or("1MM_All", String::as_str) + .parse() + .unwrap_or(UmiDedup::OneMmAll), + filtering: params + .solo_umi_filtering + .first() + .map_or("-", String::as_str) + .parse() + .unwrap_or(UmiFiltering::None), + umi_len: params.solo_umi_len as usize, + pseudocount: f64::from(u8::from( + params.solo_cb_match_wl_type.contains("pseudocounts"), + )), + } + } +} + +/// Deduplicate one feature's records into per-cell counts, cb-ascending (entries +/// gene-ascending). Mirrors STAR's `SoloFeature_collapseUMIall.cpp`: resolved +/// 1MM_multi barcodes are folded in, the flat record list is sorted by cell +/// barcode so each cell's reads are contiguous, then each cell is collapsed +/// independently (peak memory is one cell's `umi → gene` maps, not a global +/// nest over all records). Consumes the recorder's records. +pub(crate) fn dedup_cells( ctx: &SoloContext, recorder: &crate::solo::SoloRecorder, - method: UmiDedup, - filtering: UmiFiltering, - umi_len: usize, - pseudocount: f64, + opts: &CountOptions, +) -> Vec { + let &CountOptions { + method, + filtering, + umi_len, + pseudocount, + } = opts; + // Move records out of the recorder; fold in resolved 1MM_multi cells. + let mut records = std::mem::take(&mut *recorder.records.lock().unwrap()); + let exact_counts = ctx.whitelist.exact_count_snapshot(); + let multi = std::mem::take(&mut *recorder.multi_records.lock().unwrap()); + for m in &multi { + if let Some(cb) = resolve_multi_cb(&m.candidates, &exact_counts, pseudocount) { + records.push(SoloCountRecord { + cb, + umi: m.umi, + gene: m.gene, + }); + } + } + drop(multi); + + // Group each cell's reads together (parallel sort — the record vec is large). + use rayon::prelude::*; + records.par_sort_unstable_by_key(|r| r.cb); + + // Per-cell dedup is independent across cells, so run it in parallel; the + // result stays CB-ascending, which is what both output formats expect. + cell_bounds(&records, |r| r.cb) + .par_iter() + .map(|&(i, j)| { + // umi → gene → read multiplicity, for this cell only. + let mut umi_genes: HashMap> = HashMap::default(); + for r in &records[i..j] { + *umi_genes + .entry(r.umi) + .or_default() + .entry(r.gene) + .or_insert(0) += 1; + } + + // (gene → (umi → read_count)) after multi-gene UMI filtering. + let mut gene_umis: HashMap> = HashMap::default(); + for (&umi, genes) in &umi_genes { + for (&gene, &rc) in filter_multi_gene_umi(genes, filtering) { + *gene_umis.entry(gene).or_default().entry(umi).or_insert(0) += rc; + } + } + + // Collapse UMIs per gene, then emit this cell's entries gene-ascending. + let mut entries: Vec<(u32, u64)> = gene_umis + .iter() + .map(|(&gene, umis)| (gene, dedup_count(umis, method, umi_len))) + .filter(|&(_, c)| c > 0) + .collect(); + entries.sort_unstable_by_key(|&(g, _)| g); + CellCounts { + cb: records[i].cb, + n_reads: (j - i) as u64, + entries, + } + }) + .collect() +} + +/// One contiguous `[start, end)` slice per distinct key in a key-sorted slice. +fn cell_bounds(sorted: &[T], key: impl Fn(&T) -> u32) -> Vec<(usize, usize)> { + let mut bounds = Vec::new(); + let mut i = 0; + while i < sorted.len() { + let k = key(&sorted[i]); + let mut j = i + 1; + while j < sorted.len() && key(&sorted[j]) == k { + j += 1; + } + bounds.push((i, j)); + i = j; + } + bounds +} + +/// Write per-cell counts to a plain temporary MatrixMarket *body* +/// (`gene+1 cb+1 count`, barcode-ascending) and collect per-cell stats. The body +/// is finalized into `raw/` (and optionally `filtered/`) by the caller, which +/// lets the raw + filtered matrices share one pass. +fn build_matrix_body( + cells: &[CellCounts], dir: &Path, n_features: usize, ) -> Result<(tempfile::NamedTempFile, MatrixStats), Error> { @@ -350,116 +491,17 @@ fn build_matrix_body( let mut nnz = 0usize; let mut cell_stats: Vec = Vec::new(); let mut gene_seen = vec![false; n_features]; - { let mut body = std::io::BufWriter::new(body_tmp.as_file_mut()); - - // Move records out of the recorder; fold in resolved 1MM_multi cells. - let mut records = std::mem::take(&mut *recorder.records.lock().unwrap()); - let exact_counts = ctx.whitelist.exact_count_snapshot(); - let multi = std::mem::take(&mut *recorder.multi_records.lock().unwrap()); - for m in &multi { - if let Some(cb) = resolve_multi_cb(&m.candidates, &exact_counts, pseudocount) { - records.push(SoloCountRecord { - cb, - umi: m.umi, - gene: m.gene, - }); - } - } - drop(multi); - - // Group each cell's reads together (parallel sort — the record vec is large). - use rayon::prelude::*; - records.par_sort_unstable_by_key(|r| r.cb); - - // One contiguous [start, end) slice per CB. - let mut bounds: Vec<(usize, usize)> = Vec::new(); - let mut i = 0; - while i < records.len() { - let cb = records[i].cb; - let mut j = i + 1; - while j < records.len() && records[j].cb == cb { - j += 1; - } - bounds.push((i, j)); - i = j; - } - - // Per-cell dedup + MatrixMarket formatting is independent across cells, so - // run it in parallel and emit the pre-formatted bodies sequentially in CB - // order. This keeps the matrix byte-identical to the serial version. - struct CellOut { - body: Vec, - stat: Option, - genes: Vec, - } - let cell_outs: Vec = bounds - .par_iter() - .map(|&(i, j)| { - let cb = records[i].cb; - - // umi → gene → read multiplicity, for this cell only. - let mut umi_genes: HashMap> = HashMap::default(); - for r in &records[i..j] { - *umi_genes - .entry(r.umi) - .or_default() - .entry(r.gene) - .or_insert(0) += 1; - } - - // (gene → (umi → read_count)) after multi-gene UMI filtering. - let mut gene_umis: HashMap> = HashMap::default(); - for (&umi, genes) in &umi_genes { - for (&gene, &rc) in filter_multi_gene_umi(genes, filtering) { - *gene_umis.entry(gene).or_default().entry(umi).or_insert(0) += rc; - } - } - - // Collapse UMIs per gene, then emit this cell's entries gene-ascending. - let mut cell_entries: Vec<(u32, u64)> = Vec::with_capacity(gene_umis.len()); - for (&gene, umis) in &gene_umis { - let count = dedup_count(umis, method, umi_len); - if count > 0 { - cell_entries.push((gene, count)); - } - } - cell_entries.sort_unstable_by_key(|&(g, _)| g); - - let n_reads = (j - i) as u64; - let n_genes = cell_entries.len() as u32; - let mut n_umis = 0u64; - let mut cbody: Vec = Vec::new(); - let mut genes: Vec = Vec::with_capacity(cell_entries.len()); - for (g, c) in &cell_entries { - n_umis += *c; - genes.push(*g); - let _ = writeln!(cbody, "{} {} {}", g + 1, cb + 1, c); - } - let stat = (n_umis > 0).then_some(CellStat { - cb, - n_reads, - n_umis, - n_genes, - }); - CellOut { - body: cbody, - stat, - genes, - } - }) - .collect(); - - // Sequential merge: byte order preserved (CB-ascending, gene-ascending). - for co in cell_outs { - body.write_all(&co.body).map_err(|e| Error::io(e, dir))?; - nnz += co.genes.len(); - for g in co.genes { + for cell in cells { + for &(g, c) in &cell.entries { + writeln!(body, "{} {} {}", g + 1, cell.cb + 1, c).map_err(|e| Error::io(e, dir))?; gene_seen[g as usize] = true; } - if let Some(s) = co.stat { - cell_stats.push(s); + nnz += cell.entries.len(); + let stat = cell.stat(); + if stat.n_umis > 0 { + cell_stats.push(stat); } } body.flush().map_err(|e| Error::io(e, dir))?; @@ -476,6 +518,30 @@ fn build_matrix_body( )) } +/// Per-cell counts as a cells × features CSR. Rows span the **whole whitelist** +/// (`n_rows`), so row index == whitelist barcode index and every matrix in the +/// run shares one `obs` axis; barcodes with no counts are empty rows. +#[cfg(feature = "anndata-out")] +pub(crate) fn cells_to_csr( + cells: &[CellCounts], + n_rows: usize, + n_cols: usize, +) -> Result, Error> { + let nnz: usize = cells.iter().map(|c| c.entries.len()).sum(); + let mut row_offsets = Vec::with_capacity(n_rows + 1); + let mut col_indices = Vec::with_capacity(nnz); + let mut values = Vec::with_capacity(nnz); + for cell in cells { + // Empty rows for the whitelist barcodes between the last cell and this one. + row_offsets.resize(cell.cb as usize + 1, col_indices.len()); + col_indices.extend(cell.entries.iter().map(|&(g, _)| g as usize)); + values.extend(cell.entries.iter().map(|&(_, c)| c)); + } + row_offsets.resize(n_rows + 1, col_indices.len()); + CsrMatrix::try_from_csr_data(n_rows, n_cols, row_offsets, col_indices, values) + .map_err(|e| Error::Parameter(format!("building solo count matrix: {e}"))) +} + /// Write a final `matrix.mtx[.gz]` = MatrixMarket header + (optionally /// cb-remapped/filtered) body. With `remap = None` the body is copied verbatim /// (raw); with `Some(map)` only columns in the map survive, renumbered to the @@ -554,7 +620,7 @@ pub enum MultiMethod { } impl MultiMethod { - fn name(self) -> &'static str { + pub(crate) fn name(self) -> &'static str { match self { MultiMethod::Uniform => "Uniform", MultiMethod::Rescue => "Rescue", @@ -808,6 +874,78 @@ fn build_multi_matrices( Ok(()) } +/// CSR variant of [`build_multi_matrices`]: given the unique counts as a +/// whitelist × genes CSR (row index = cell barcode, as built by +/// [`cells_to_csr`]), yields one real-valued `UniqueAndMult` matrix per +/// `--soloMultiMappers` method, in `methods` order. Cells present only in +/// `multi_records` (no unique gene) still get their molecules distributed here — +/// unlike the `.mtx` writer, which walks the unique body and so skips them. +#[cfg(feature = "anndata-out")] +pub(crate) fn multi_matrices<'a>( + unique: &'a CsrMatrix, + multi_records: &[crate::solo::MultiGeneRecord], + methods: &'a [MultiMethod], +) -> impl Iterator), Error>> + 'a { + // Per-cell multi molecules: one gene set per deduplicated UMI. + let mut by_cb: HashMap>> = HashMap::default(); + for r in multi_records { + by_cb + .entry(r.cb) + .or_default() + .entry(r.umi) + .or_default() + .extend(r.genes.iter().copied()); + } + let mols: HashMap>> = by_cb + .into_iter() + .map(|(cb, umis)| { + ( + cb, + umis.into_values() + .map(|g| g.into_iter().collect()) + .collect(), + ) + }) + .collect(); + + methods.iter().map(move |&m| { + let mut row_offsets = Vec::with_capacity(unique.nrows() + 1); + let mut col_indices = Vec::with_capacity(unique.nnz()); + let mut values = Vec::with_capacity(unique.nnz()); + row_offsets.push(0); + for (cb, row) in unique.row_iter().enumerate() { + let cell_mols = mols.get(&(cb as u32)).map_or(&[][..], Vec::as_slice); + if row.nnz() == 0 && cell_mols.is_empty() { + row_offsets.push(col_indices.len()); + continue; + } + let u: HashMap = row + .col_indices() + .iter() + .zip(row.values()) + .map(|(&g, &v)| (g as u32, v as f64)) + .collect(); + let mut entries: Vec<(u32, f64)> = distribute_multi(m, &u, cell_mols) + .into_iter() + .filter(|&(_, v)| v > 1e-9) + .collect(); + entries.sort_unstable_by_key(|&(g, _)| g); + col_indices.extend(entries.iter().map(|&(g, _)| g as usize)); + values.extend(entries.iter().map(|&(_, v)| v)); + row_offsets.push(col_indices.len()); + } + CsrMatrix::try_from_csr_data( + row_offsets.len() - 1, + unique.ncols(), + row_offsets, + col_indices, + values, + ) + .map(|csr| (m, csr)) + .map_err(|e| Error::Parameter(format!("building UniqueAndMult-{} matrix: {e}", m.name()))) + }) +} + /// Apply `--soloUMIfiltering` to the gene→read_count map of a single UMI, /// returning the surviving (gene, read_count) entries. fn filter_multi_gene_umi(genes: &HashMap, filtering: UmiFiltering) -> Vec<(&u32, &u32)> { @@ -843,7 +981,7 @@ fn knee_cr22(umis_desc: &[u64], n_expected: usize, max_pct: f64, max_min_ratio: /// Whitelist indices of called cells (sorted ascending) per `--soloCellFilter`. /// `None` → no filtered/ output. `EmptyDrops_CR` writes only the knee-guaranteed /// cells here (the Monte-Carlo rescue is the standalone `emptydrops` binary). -fn called_cells(cells: &[CellStat], filter: &[String]) -> Option> { +pub(crate) fn called_cells(cells: &[CellStat], filter: &[String]) -> Option> { let method = filter.first().map_or("CellRanger2.2", String::as_str); let arg = |i: usize, d: f64| filter.get(i).and_then(|s| s.parse().ok()).unwrap_or(d); let mut cbs: Vec = match method { @@ -877,13 +1015,14 @@ fn called_cells(cells: &[CellStat], filter: &[String]) -> Option> { /// `--soloCellFilter EmptyDrops_CR`: the CR2.2-knee guaranteed cells PLUS cells /// rescued by the EmptyDrops multinomial Monte-Carlo test (STAR -/// `SoloFeature_emptyDrops_CR.cpp`). Per-cell gene profiles for the ambient + -/// candidate cells are read back from the raw matrix body. `filter` is the +/// `SoloFeature_emptyDrops_CR.cpp`). `triplets` streams the raw counts as 0-based +/// `(gene, cb, count)` — only the ambient + candidate cells are kept, so it need +/// not be materialized. `filter` is the /// `EmptyDrops_CR nExpected maxPct maxMinRatio indMin indMax umiMin /// umiMinFracMedian candMaxN FDR [simN]` argument list. -fn emptydrops_called( +pub(crate) fn emptydrops_called( cells: &[CellStat], - body: &tempfile::NamedTempFile, + triplets: impl Iterator>, n_features: usize, filter: &[String], ) -> Result, Error> { @@ -931,21 +1070,12 @@ fn emptydrops_called( .map(|c| c.cb) .collect(); - // Re-read the raw body for ambient (summed) + per-candidate profiles. + // Stream the raw counts for ambient (summed) + per-candidate profiles. let mut ambient = vec![0f64; n_features]; let mut amb_total = 0f64; let mut cand_profiles: HashMap> = HashMap::default(); - let reader = - BufReader::new(std::fs::File::open(body.path()).map_err(|e| Error::io(e, body.path()))?); - for line in reader.lines() { - let line = line.map_err(|e| Error::io(e, body.path()))?; - let mut it = line.split(' '); - let (Some(gt), Some(ct), Some(vt)) = (it.next(), it.next(), it.next()) else { - continue; - }; - let g = gt.parse::().unwrap_or(1) - 1; - let cb = ct.parse::().unwrap_or(1) - 1; - let v = vt.parse::().unwrap_or(0); + for t in triplets { + let (g, cb, v) = t?; if ambient_set.contains(&cb) { ambient[g as usize] += v as f64; amb_total += v as f64; @@ -1078,6 +1208,29 @@ fn emptydrops_called( Ok(called) } +/// Stream a MatrixMarket body (`gene+1 cb+1 count` per line) as 0-based +/// `(gene, cb, count)` triplets, for `emptydrops_called`. +fn body_triplets( + path: &Path, +) -> Result>, Error> { + let path = path.to_path_buf(); + let reader = BufReader::new(std::fs::File::open(&path).map_err(|e| Error::io(e, &path))?); + Ok(reader.lines().filter_map(move |line| match line { + Err(e) => Some(Err(Error::io(e, &path))), + Ok(l) => { + let mut it = l.split(' '); + let (Some(gt), Some(ct), Some(vt)) = (it.next(), it.next(), it.next()) else { + return None; + }; + Some(Ok(( + gt.parse::().unwrap_or(1) - 1, + ct.parse::().unwrap_or(1) - 1, + vt.parse::().unwrap_or(0), + ))) + } + })) +} + /// Median of an ascending-sorted slice (0 if empty). fn median_sorted(sorted: &[u64]) -> u64 { let n = sorted.len(); @@ -1092,7 +1245,7 @@ fn median_sorted(sorted: &[u64]) -> u64 { /// Write the raw gene-count matrix + `Summary.csv` for a finished solo run. /// No-op (with a warning) when there is no explicit whitelist. -pub fn write_gene_matrix( +pub fn write_matrix_market( ctx: &SoloContext, params: &crate::params::Parameters, align_stats: &crate::stats::AlignmentStats, @@ -1106,25 +1259,8 @@ pub fn write_gene_matrix( return Ok(()); }; - let method: UmiDedup = params - .solo_umi_dedup - .first() - .map_or("1MM_All", String::as_str) - .parse() - .unwrap_or(UmiDedup::OneMmAll); - let filtering: UmiFiltering = params - .solo_umi_filtering - .first() - .map_or("-", String::as_str) - .parse() - .unwrap_or(UmiFiltering::None); - // `*_pseudocounts` CB-match types add 1 to the posterior prior. - let pseudocount = if params.solo_cb_match_wl_type.contains("pseudocounts") { - 1.0 - } else { - 0.0 - }; - let umi_len = params.solo_umi_len as usize; + let opts = CountOptions::from_params(params); + let umi_len = opts.umi_len; let solo_dir = params .solo_out_file_names @@ -1147,31 +1283,7 @@ pub fn write_gene_matrix( .cloned() .unwrap_or_else(|| "matrix.mtx".to_string()); - // Global mapping funnel (shared across features). The region tallies are - // CellRanger-style positional bins over uniquely-mapped reads, populated only - // when both Gene and GeneFull run (otherwise the split is unavailable). - use std::sync::atomic::Ordering; - let total_reads = align_stats.total_reads.load(Ordering::Relaxed); - let mapped_unique = align_stats.uniquely_mapped.load(Ordering::Relaxed); - let mapped_multi = align_stats.multi_mapped.load(Ordering::Relaxed); - let valid_barcodes = ctx.stats.yes_exact.load(Ordering::Relaxed) - + ctx.stats.yes_one_mm.load(Ordering::Relaxed) - + ctx.stats.yes_mult_mm.load(Ordering::Relaxed); - let reads_of = |f: crate::solo::SoloFeature| -> u64 { - ctx.features - .iter() - .position(|&x| x == f) - .map_or(0, |i| ctx.feature_reads[i].load(Ordering::Relaxed)) - }; - let have_funnel = ctx.features.contains(&crate::solo::SoloFeature::Gene) - && ctx.features.contains(&crate::solo::SoloFeature::GeneFull); - let region = have_funnel.then(|| RegionFunnel { - exonic: ctx.region_stats.exonic.load(Ordering::Relaxed), - intronic: ctx.region_stats.intronic.load(Ordering::Relaxed), - intergenic: ctx.region_stats.intergenic.load(Ordering::Relaxed), - antisense: ctx.region_stats.antisense.load(Ordering::Relaxed), - }); - + let funnel = MappingFunnel::collect(ctx, align_stats); let gzip = matches!(params.solo_out_gzip.as_str(), "yes" | "Yes" | "true"); let n_genes = ctx.gene_ann.gene_ids.len(); let multi_methods = MultiMethod::parse_list(¶ms.solo_multi_mappers); @@ -1182,18 +1294,11 @@ pub fn write_gene_matrix( let raw_dir = feature_dir.join("raw"); std::fs::create_dir_all(&raw_dir).map_err(|e| Error::io(e, &raw_dir))?; - // Stream the deduplicated counts into a shared temp body, then finalize - // the raw matrix (and the filtered one below) from it. - let (body, mstats) = build_matrix_body( - ctx, - recorder, - method, - filtering, - umi_len, - pseudocount, - &raw_dir, - n_genes, - )?; + // Collapse UMIs, then write the deduplicated counts into a shared temp + // body and finalize the raw matrix (and the filtered one below) from it. + let cells = dedup_cells(ctx, recorder, &opts); + let (body, mstats) = build_matrix_body(&cells, &raw_dir, n_genes)?; + drop(cells); write_features( &raw_dir.join(&features_name), &ctx.gene_ann.gene_ids, @@ -1233,7 +1338,7 @@ pub fn write_gene_matrix( { Some(emptydrops_called( &mstats.cells, - &body, + body_triplets(body.path())?, n_genes, ¶ms.solo_cell_filter, )?) @@ -1299,23 +1404,15 @@ pub fn write_gene_matrix( &feature_dir.join("Summary.csv"), feature.dir_name(), &mstats, - total_reads, - valid_barcodes, - mapped_unique, - mapped_multi, - reads_of(*feature), + &funnel, + feature_reads(ctx, *feature), )?; log::info!("STARsolo: wrote {}/Summary.csv", feature.dir_name()); // CellRanger-style mapping funnel goes in a SEPARATE additional file so the // faithful Summary.csv is never altered (PR #90 review: keep this release a // drop-in faithful port; output-changing features come later). - if let Some(r) = region { - write_cellranger_summary( - &feature_dir.join("CellRanger.summary.csv"), - total_reads, - mapped_unique, - r, - )?; + if let Some(cr) = funnel.cellranger_summary() { + write_cellranger_summary(&feature_dir.join("CellRanger.summary.csv"), &cr)?; log::info!( "STARsolo: wrote {}/CellRanger.summary.csv", feature.dir_name() @@ -1345,12 +1442,9 @@ pub fn write_gene_matrix( sorted.len(), gzip, )?; - let umi_len = params.solo_umi_len as usize; + let cells = sj_cells(&ctx.sj_records.lock().unwrap(), &row, opts.method, umi_len); let nnz = build_sj_matrix( - &ctx.sj_records.lock().unwrap(), - &row, - method, - umi_len, + &cells, &sj_dir.join(&matrix_name), order.len(), sorted.len(), @@ -1380,17 +1474,20 @@ pub fn write_gene_matrix( sorted.len(), gzip, )?; - let umi_len = params.solo_umi_len as usize; // `--soloVelocytoAmbiguous no` folds exon-only molecules into spliced and // omits ambiguous.mtx (rustar extension); default `yes` = STARsolo 3-matrix. let keep_ambiguous = !matches!( params.solo_velocyto_ambiguous.as_str(), "no" | "No" | "false" ); - let nnz = build_velocyto_matrices( + let cells = velocyto_cells( &ctx.velocyto_records.lock().unwrap(), - method, + opts.method, umi_len, + keep_ambiguous, + ); + let nnz = build_velocyto_matrices( + &cells, &velo_dir, n_genes, sorted.len(), @@ -1415,45 +1512,29 @@ pub fn write_gene_matrix( Ok(()) } -/// Build the SJ feature matrix from (cell, UMI, junction) records, mapping each -/// junction's absolute intron coords to its `SJ.out.tab` row and UMI-collapsing -/// per (cell, junction). Junctions not in `row` (filtered out of SJ.out.tab) are -/// dropped. Same MatrixMarket layout as the gene matrix (junctions are rows). -#[allow(clippy::too_many_arguments)] -fn build_sj_matrix( +/// Collapse the SJ feature's (cell, UMI, junction) records into per-cell counts, +/// mapping each junction's absolute intron coords to its `SJ.out.tab` row. +/// Junctions not in `row` (filtered out of SJ.out.tab) are dropped. Same shape +/// as [`dedup_cells`]: cb-ascending cells, junction-ascending entries. +pub(crate) fn sj_cells( records: &[crate::solo::SjCountRecord], row: &HashMap<(u64, u64), u32>, method: UmiDedup, umi_len: usize, - matrix_path: &Path, - n_junctions: usize, - n_barcodes: usize, - gzip: bool, -) -> Result { - // Group by cell barcode (ascending column order). +) -> Vec { use rayon::prelude::*; let mut recs: Vec<&crate::solo::SjCountRecord> = records.iter().collect(); recs.par_sort_unstable_by_key(|r| r.cb); - let dir = matrix_path.parent().unwrap_or_else(|| Path::new(".")); - let mut body_tmp = tempfile::Builder::new() - .prefix(".sj_body") - .tempfile_in(dir) - .map_err(|e| Error::io(e, dir))?; - let mut nnz = 0usize; - { - let mut body = std::io::BufWriter::new(body_tmp.as_file_mut()); - let mut i = 0; - while i < recs.len() { - let cb = recs[i].cb; + cell_bounds(&recs, |r| r.cb) + .par_iter() + .map(|&(i, j)| { // junction row → (umi → read count) for this cell. let mut sj_umis: HashMap> = HashMap::default(); - while i < recs.len() && recs[i].cb == cb { - let r = recs[i]; + for r in &recs[i..j] { if let Some(&rw) = row.get(&(r.intron_start, r.intron_end)) { *sj_umis.entry(rw).or_default().entry(r.umi).or_insert(0) += 1; } - i += 1; } let mut entries: Vec<(u32, u64)> = sj_umis .into_iter() @@ -1461,8 +1542,36 @@ fn build_sj_matrix( .filter(|&(_, c)| c > 0) .collect(); entries.sort_unstable_by_key(|&(rw, _)| rw); - for (rw, c) in entries { - writeln!(body, "{} {} {}", rw + 1, cb + 1, c).map_err(|e| Error::io(e, dir))?; + CellCounts { + cb: recs[i].cb, + n_reads: (j - i) as u64, + entries, + } + }) + .collect() +} + +/// Write the SJ feature matrix (junctions are rows) in the same MatrixMarket +/// layout as the gene matrix. +fn build_sj_matrix( + cells: &[CellCounts], + matrix_path: &Path, + n_junctions: usize, + n_barcodes: usize, + gzip: bool, +) -> Result { + let dir = matrix_path.parent().unwrap_or_else(|| Path::new(".")); + let mut body_tmp = tempfile::Builder::new() + .prefix(".sj_body") + .tempfile_in(dir) + .map_err(|e| Error::io(e, dir))?; + let mut nnz = 0usize; + { + let mut body = std::io::BufWriter::new(body_tmp.as_file_mut()); + for cell in cells { + for &(rw, c) in &cell.entries { + writeln!(body, "{} {} {}", rw + 1, cell.cb + 1, c) + .map_err(|e| Error::io(e, dir))?; nnz += 1; } } @@ -1482,29 +1591,22 @@ fn build_sj_matrix( Ok(nnz) } -/// Build the `Velocyto` matrices from (cell, UMI, gene, category) records. Per -/// (cell, gene) each UMI is resolved to one category (priority unspliced over -/// spliced over ambiguous — any intron evidence makes the molecule nascent), then -/// UMI-deduplicated per category. Genes are rows, cells columns — same layout as -/// the Gene matrix, written as files scVelo/dynamo ingest directly. +/// Collapse the `Velocyto` (cell, UMI, gene, category) records into per-cell +/// counts, one [`CellCounts`] per category in `[spliced, unspliced, ambiguous]` +/// order. Per (cell, gene) each UMI is resolved to a single category (priority +/// unspliced over spliced over ambiguous — any intron evidence makes the molecule +/// nascent), then UMI-deduplicated within that category. /// -/// With `keep_ambiguous` (default, STARsolo-faithful) three matrices are written: -/// `spliced`/`unspliced`/`ambiguous`. With `keep_ambiguous = false` the exon-only -/// `ambiguous` molecules are folded into `spliced` (an exon-only read is most -/// likely mature mRNA; cf. He, Soneson & Patro 2023) and only `spliced`/`unspliced` -/// are written — no `ambiguous.mtx`. The returned `[usize; 3]` always reports -/// `[spliced, unspliced, ambiguous]` nnz (ambiguous is 0 when folded). -#[allow(clippy::too_many_arguments)] -fn build_velocyto_matrices( +/// With `keep_ambiguous` (default, STARsolo-faithful) all three categories are +/// kept. With `keep_ambiguous = false` the exon-only `ambiguous` molecules are +/// folded into `spliced` (an exon-only read is most likely mature mRNA; cf. He, +/// Soneson & Patro 2023) and the ambiguous counts come back empty. +pub(crate) fn velocyto_cells( records: &[crate::solo::VelocytoRecord], method: UmiDedup, umi_len: usize, - dir: &Path, - n_genes: usize, - n_barcodes: usize, - gzip: bool, keep_ambiguous: bool, -) -> Result<[usize; 3], Error> { +) -> Vec<[CellCounts; 3]> { use crate::solo::VelocytoCategory; // Category → matrix index (file order) and resolution priority. let cat_idx = |c: VelocytoCategory| match c { @@ -1517,30 +1619,12 @@ fn build_velocyto_matrices( VelocytoCategory::Spliced => 1, VelocytoCategory::Ambiguous => 0, }; - let names = ["spliced.mtx", "unspliced.mtx", "ambiguous.mtx"]; - let mut recs: Vec<&crate::solo::VelocytoRecord> = records.iter().collect(); use rayon::prelude::*; + let mut recs: Vec<&crate::solo::VelocytoRecord> = records.iter().collect(); recs.par_sort_unstable_by_key(|r| r.cb); - // One contiguous [start, end) slice per CB. - let mut bounds: Vec<(usize, usize)> = Vec::new(); - let mut i = 0; - while i < recs.len() { - let cb = recs[i].cb; - let mut j = i + 1; - while j < recs.len() && recs[j].cb == cb { - j += 1; - } - bounds.push((i, j)); - i = j; - } - - // Per-cell dedup is independent across cells → run in parallel, each cell - // producing the three matrices' lines (gene-ascending). Merge sequentially - // in CB order so the three .mtx files stay byte-identical to the serial path. - type VeloCellOut = ([Vec; 3], [usize; 3]); - let cell_outs: Vec = bounds + cell_bounds(&recs, |r| r.cb) .par_iter() .map(|&(lo, hi)| { let cb = recs[lo].cb; @@ -1561,13 +1645,15 @@ fn build_velocyto_matrices( // Per gene, dedup UMIs within each resolved category, emit entries. let mut genes: Vec<&u32> = gene_umi.keys().collect(); genes.sort_unstable(); - let mut bufs: [Vec; 3] = [Vec::new(), Vec::new(), Vec::new()]; - let mut cnt = [0usize; 3]; + let mut out = std::array::from_fn::<_, 3, _>(|_| CellCounts { + cb, + n_reads: (hi - lo) as u64, + entries: Vec::new(), + }); for &g in &genes { - let umis = &gene_umi[g]; let mut by_cat: [HashMap; 3] = [HashMap::default(), HashMap::default(), HashMap::default()]; - for (&umi, &(cat, rc)) in umis { + for (&umi, &(cat, rc)) in &gene_umi[g] { by_cat[cat_idx(cat)].insert(umi, rc); } // Fold ambiguous (exon-only) molecules into spliced. A UMI resolves @@ -1576,18 +1662,31 @@ fn build_velocyto_matrices( let amb = std::mem::take(&mut by_cat[2]); by_cat[0].extend(amb); } - for (k, buf) in bufs.iter_mut().enumerate() { + for (k, cell) in out.iter_mut().enumerate() { let c = dedup_count(&by_cat[k], method, umi_len); if c > 0 { - let _ = writeln!(buf, "{} {} {}", g + 1, cb + 1, c); - cnt[k] += 1; + cell.entries.push((*g, c)); } } } - (bufs, cnt) + out }) - .collect(); + .collect() +} +/// Write the three `Velocyto` matrices (genes are rows, cells columns — the same +/// layout as the Gene matrix, which scVelo/dynamo ingest directly). Only +/// `spliced`/`unspliced` are written when ambiguous molecules were folded in. +/// The returned `[usize; 3]` reports `[spliced, unspliced, ambiguous]` nnz. +fn build_velocyto_matrices( + cells: &[[CellCounts; 3]], + dir: &Path, + n_genes: usize, + n_barcodes: usize, + gzip: bool, + keep_ambiguous: bool, +) -> Result<[usize; 3], Error> { + let names = ["spliced.mtx", "unspliced.mtx", "ambiguous.mtx"]; let mut bodies: Vec = Vec::new(); for _ in 0..3 { bodies.push( @@ -1603,10 +1702,13 @@ fn build_velocyto_matrices( .iter_mut() .map(|t| std::io::BufWriter::new(t.as_file_mut())) .collect(); - for (bufs, cnt) in &cell_outs { + for per_cat in cells { for (k, w) in writers.iter_mut().enumerate() { - w.write_all(&bufs[k]).map_err(|e| Error::io(e, dir))?; - nnz[k] += cnt[k]; + for &(g, c) in &per_cat[k].entries { + writeln!(w, "{} {} {}", g + 1, per_cat[k].cb + 1, c) + .map_err(|e| Error::io(e, dir))?; + nnz[k] += 1; + } } } for w in &mut writers { @@ -1640,128 +1742,239 @@ struct RegionFunnel { antisense: u64, } +/// Reads uniquely assigned to `feature` among valid-barcode reads — the STARsolo +/// "Reads Mapped to : Unique" metric. +pub(crate) fn feature_reads(ctx: &SoloContext, feature: crate::solo::SoloFeature) -> u64 { + use std::sync::atomic::Ordering; + ctx.features + .iter() + .position(|&x| x == feature) + .map_or(0, |i| ctx.feature_reads[i].load(Ordering::Relaxed)) +} + +/// The global read funnel every feature's `Summary.csv` is computed against — +/// counted once per run, then shared across features and output formats. +pub(crate) struct MappingFunnel { + pub total_reads: u64, + pub valid_barcodes: u64, + pub mapped_unique: u64, + pub mapped_multi: u64, + /// The positional (exonic/intronic/…) split, available only when both Gene + /// and GeneFull ran — otherwise there is nothing to compare them against. + region: Option, +} + +impl MappingFunnel { + pub(crate) fn collect(ctx: &SoloContext, align_stats: &crate::stats::AlignmentStats) -> Self { + use std::sync::atomic::Ordering; + let have_funnel = ctx.features.contains(&crate::solo::SoloFeature::Gene) + && ctx.features.contains(&crate::solo::SoloFeature::GeneFull); + Self { + total_reads: align_stats.total_reads.load(Ordering::Relaxed), + valid_barcodes: ctx.stats.yes_exact.load(Ordering::Relaxed) + + ctx.stats.yes_one_mm.load(Ordering::Relaxed) + + ctx.stats.yes_mult_mm.load(Ordering::Relaxed), + mapped_unique: align_stats.uniquely_mapped.load(Ordering::Relaxed), + mapped_multi: align_stats.multi_mapped.load(Ordering::Relaxed), + region: have_funnel.then(|| RegionFunnel { + exonic: ctx.region_stats.exonic.load(Ordering::Relaxed), + intronic: ctx.region_stats.intronic.load(Ordering::Relaxed), + intergenic: ctx.region_stats.intergenic.load(Ordering::Relaxed), + antisense: ctx.region_stats.antisense.load(Ordering::Relaxed), + }), + } + } + + /// `num` as a fraction of all input reads. + fn frac(&self, num: u64) -> f64 { + if self.total_reads == 0 { + 0.0 + } else { + num as f64 / self.total_reads as f64 + } + } + + /// The CellRanger positional funnel, when both Gene and GeneFull ran. + pub(crate) fn cellranger_summary(&self) -> Option { + let r = self.region?; + Some(CellRangerSummary { + n_reads: self.total_reads, + frac_mapped_genome_unique: self.frac(self.mapped_unique), + frac_exonic: self.frac(r.exonic), + frac_intronic: self.frac(r.intronic), + frac_intergenic: self.frac(r.intergenic), + frac_antisense: self.frac(r.antisense), + }) + } +} + /// Write the STARsolo-faithful `Summary.csv` for one feature: the sequencing / /// genome-mapping rows plus per-cell UMI/gene statistics over the CR2.2-knee-called /// cells. The CellRanger-style exonic/intronic/intergenic/antisense funnel is a /// rustar extension kept in a SEPARATE file (see `write_cellranger_summary`) so /// this file is never altered relative to STARsolo's own Summary.csv. -#[allow(clippy::too_many_arguments)] fn write_summary( path: &Path, feature_name: &str, mstats: &MatrixStats, - total_reads: u64, - valid_barcodes: u64, - mapped_unique: u64, - mapped_multi: u64, + funnel: &MappingFunnel, feature_mapped: u64, ) -> Result<(), Error> { - let frac = |num: u64| -> f64 { - if total_reads == 0 { - 0.0 - } else { - num as f64 / total_reads as f64 - } - }; - - // Cell calling: CR2.2 knee on per-barcode UMI totals. - let mut umis_desc: Vec = mstats.cells.iter().map(|c| c.n_umis).collect(); - umis_desc.sort_unstable_by(|a, b| b.cmp(a)); - let thr = knee_cr22(&umis_desc, 3000, 0.99, 10.0); - let cells: Vec<&CellStat> = mstats.cells.iter().filter(|c| c.n_umis >= thr).collect(); - let n_cells = cells.len(); - - // Totals across all barcodes (for sequencing saturation + fraction-in-cells). - let total_reads_counted: u64 = mstats.cells.iter().map(|c| c.n_reads).sum(); - let total_umis_all: u64 = mstats.cells.iter().map(|c| c.n_umis).sum(); - let saturation = if total_reads_counted > 0 { - 1.0 - total_umis_all as f64 / total_reads_counted as f64 - } else { - 0.0 - }; - - // Per-cell aggregates over called cells. - let reads_in_cells: u64 = cells.iter().map(|c| c.n_reads).sum(); - let umis_in_cells: u64 = cells.iter().map(|c| c.n_umis).sum(); - let mut reads_sorted: Vec = cells.iter().map(|c| c.n_reads).collect(); - let mut umis_sorted: Vec = cells.iter().map(|c| c.n_umis).collect(); - let mut genes_sorted: Vec = cells.iter().map(|c| c.n_genes as u64).collect(); - reads_sorted.sort_unstable(); - umis_sorted.sort_unstable(); - genes_sorted.sort_unstable(); - let mean = |sum: u64| -> u64 { - if n_cells == 0 { - 0 - } else { - sum / n_cells as u64 - } - }; - + let s = feature_summary(&mstats.cells, mstats.genes_detected, funnel, feature_mapped); use std::fmt::Write as _; let mut out = String::new(); let mut row = |k: &str, v: String| { let _ = writeln!(out, "{k},{v}"); }; - row("Number of Reads", total_reads.to_string()); + row("Number of Reads", s.n_reads.to_string()); row( "Reads With Valid Barcodes", - format!("{:.6}", frac(valid_barcodes)), + format!("{:.6}", s.frac_valid_barcodes), ); - row("Sequencing Saturation", format!("{saturation:.6}")); + row("Sequencing Saturation", format!("{:.6}", s.saturation)); row( "Reads Mapped to Genome: Unique+Multiple", - format!("{:.6}", frac(mapped_unique + mapped_multi)), + format!("{:.6}", s.frac_mapped_genome_unique_multi), ); row( "Reads Mapped to Genome: Unique", - format!("{:.6}", frac(mapped_unique)), + format!("{:.6}", s.frac_mapped_genome_unique), ); row( &format!("Reads Mapped to {feature_name}: Unique {feature_name}"), - format!("{:.6}", frac(feature_mapped)), + format!("{:.6}", s.frac_mapped_feature_unique), ); - row("Estimated Number of Cells", n_cells.to_string()); + row("Estimated Number of Cells", s.n_cells.to_string()); row( &format!("Unique Reads in Cells Mapped to {feature_name}"), - reads_in_cells.to_string(), + s.reads_in_cells.to_string(), ); row( "Fraction of Unique Reads in Cells", - format!( - "{:.6}", - if total_reads_counted > 0 { - reads_in_cells as f64 / total_reads_counted as f64 - } else { - 0.0 - } - ), - ); - row("Mean Reads per Cell", mean(reads_in_cells).to_string()); - row( - "Median Reads per Cell", - median_sorted(&reads_sorted).to_string(), - ); - row("UMIs in Cells", umis_in_cells.to_string()); - row("Mean UMI per Cell", mean(umis_in_cells).to_string()); - row( - "Median UMI per Cell", - median_sorted(&umis_sorted).to_string(), + format!("{:.6}", s.frac_reads_in_cells), ); + row("Mean Reads per Cell", s.mean_reads_per_cell.to_string()); + row("Median Reads per Cell", s.median_reads_per_cell.to_string()); + row("UMIs in Cells", s.umis_in_cells.to_string()); + row("Mean UMI per Cell", s.mean_umis_per_cell.to_string()); + row("Median UMI per Cell", s.median_umis_per_cell.to_string()); row( &format!("Mean {feature_name} per Cell"), - mean(genes_sorted.iter().sum()).to_string(), + s.mean_features_per_cell.to_string(), ); row( &format!("Median {feature_name} per Cell"), - median_sorted(&genes_sorted).to_string(), + s.median_features_per_cell.to_string(), ); row( &format!("Total {feature_name} Detected"), - mstats.genes_detected.to_string(), + s.features_detected.to_string(), ); + std::fs::write(path, out).map_err(|e| Error::io(e, path)) +} - std::fs::write(path, out).map_err(|e| Error::io(e, path))?; - Ok(()) +/// The `Summary.csv` statistics for one feature, before any formatting: fractions +/// are raw ratios in `[0, 1]`, counts are counts. `write_summary` renders these as +/// the STARsolo CSV; other output formats (AnnData) can consume them directly. +#[derive(Clone, Copy, Debug)] +pub struct FeatureSummary { + /// Reads in the input (all barcodes, mapped or not). + pub n_reads: u64, + /// Fraction of reads whose CB matched the whitelist. + pub frac_valid_barcodes: f64, + /// 1 − UMIs / reads over all barcodes. + pub saturation: f64, + /// Fraction of reads mapped to the genome, uniquely or multi-mapping. + pub frac_mapped_genome_unique_multi: f64, + /// Fraction of reads mapped uniquely to the genome. + pub frac_mapped_genome_unique: f64, + /// Fraction of reads assigned uniquely to this feature (gene / SJ / …). + pub frac_mapped_feature_unique: f64, + /// Cells called by the CR2.2 knee on per-barcode UMI totals. + pub n_cells: usize, + /// Feature-assigned reads in called cells. + pub reads_in_cells: u64, + /// `reads_in_cells` over the feature-assigned reads of all barcodes. + pub frac_reads_in_cells: f64, + pub mean_reads_per_cell: u64, + pub median_reads_per_cell: u64, + /// Deduplicated UMIs in called cells. + pub umis_in_cells: u64, + pub mean_umis_per_cell: u64, + pub median_umis_per_cell: u64, + pub mean_features_per_cell: u64, + pub median_features_per_cell: u64, + /// Features with a nonzero count anywhere in the raw matrix. + pub features_detected: u32, +} + +/// Compute [`FeatureSummary`] from the per-cell stats plus the global mapping +/// funnel. `cell_stats` is the raw (unfiltered) per-barcode set; cell calling +/// happens here. +pub(crate) fn feature_summary( + cell_stats: &[CellStat], + features_detected: u32, + funnel: &MappingFunnel, + feature_mapped: u64, +) -> FeatureSummary { + let frac = |num: u64| funnel.frac(num); + + // Cell calling: CR2.2 knee on per-barcode UMI totals. + let mut umis_desc: Vec = cell_stats.iter().map(|c| c.n_umis).collect(); + umis_desc.sort_unstable_by(|a, b| b.cmp(a)); + let thr: u64 = knee_cr22(&umis_desc, 3000, 0.99, 10.0); + let cells: Vec<&CellStat> = cell_stats.iter().filter(|c| c.n_umis >= thr).collect(); + let n_cells = cells.len(); + + // Totals across all barcodes (for sequencing saturation + fraction-in-cells). + let total_reads_counted: u64 = cell_stats.iter().map(|c| c.n_reads).sum(); + let total_umis_all: u64 = cell_stats.iter().map(|c| c.n_umis).sum(); + let saturation = if total_reads_counted > 0 { + 1.0 - total_umis_all as f64 / total_reads_counted as f64 + } else { + 0.0 + }; + + // Per-cell aggregates over called cells. + let reads_in_cells: u64 = cells.iter().map(|c| c.n_reads).sum(); + let umis_in_cells: u64 = cells.iter().map(|c| c.n_umis).sum(); + let mut reads_sorted: Vec = cells.iter().map(|c| c.n_reads).collect(); + let mut umis_sorted: Vec = cells.iter().map(|c| c.n_umis).collect(); + let mut genes_sorted: Vec = cells.iter().map(|c| c.n_genes as u64).collect(); + reads_sorted.sort_unstable(); + umis_sorted.sort_unstable(); + genes_sorted.sort_unstable(); + let mean = |sum: u64| -> u64 { + if n_cells == 0 { + 0 + } else { + sum / n_cells as u64 + } + }; + + FeatureSummary { + n_reads: funnel.total_reads, + frac_valid_barcodes: frac(funnel.valid_barcodes), + saturation, + frac_mapped_genome_unique_multi: frac(funnel.mapped_unique + funnel.mapped_multi), + frac_mapped_genome_unique: frac(funnel.mapped_unique), + frac_mapped_feature_unique: frac(feature_mapped), + n_cells, + reads_in_cells, + frac_reads_in_cells: if total_reads_counted > 0 { + reads_in_cells as f64 / total_reads_counted as f64 + } else { + 0.0 + }, + mean_reads_per_cell: mean(reads_in_cells), + median_reads_per_cell: median_sorted(&reads_sorted), + umis_in_cells, + mean_umis_per_cell: mean(umis_in_cells), + median_umis_per_cell: median_sorted(&umis_sorted), + mean_features_per_cell: mean(genes_sorted.iter().sum()), + median_features_per_cell: median_sorted(&genes_sorted), + features_detected, + } } /// Write the CellRanger-style positional mapping funnel (exonic / intronic / @@ -1769,47 +1982,48 @@ fn write_summary( /// file, so the faithful STARsolo `Summary.csv` is never altered by this rustar /// extension. Only emitted when both `Gene` and `GeneFull` run (the exonic/intronic /// split needs both queries). -fn write_cellranger_summary( - path: &Path, - total_reads: u64, - mapped_unique: u64, - region: RegionFunnel, -) -> Result<(), Error> { - let frac = |num: u64| -> f64 { - if total_reads == 0 { - 0.0 - } else { - num as f64 / total_reads as f64 - } - }; +fn write_cellranger_summary(path: &Path, s: &CellRangerSummary) -> Result<(), Error> { use std::fmt::Write as _; let mut out = String::new(); let mut row = |k: &str, v: String| { let _ = writeln!(out, "{k},{v}"); }; - row("Number of Reads", total_reads.to_string()); + row("Number of Reads", s.n_reads.to_string()); row( "Reads Mapped to Genome: Unique", - format!("{:.6}", frac(mapped_unique)), + format!("{:.6}", s.frac_mapped_genome_unique), ); row( "Reads Mapped Confidently to Exonic Regions", - format!("{:.6}", frac(region.exonic)), + format!("{:.6}", s.frac_exonic), ); row( "Reads Mapped Confidently to Intronic Regions", - format!("{:.6}", frac(region.intronic)), + format!("{:.6}", s.frac_intronic), ); row( "Reads Mapped Confidently to Intergenic Regions", - format!("{:.6}", frac(region.intergenic)), + format!("{:.6}", s.frac_intergenic), ); row( "Reads Mapped Antisense to Gene", - format!("{:.6}", frac(region.antisense)), + format!("{:.6}", s.frac_antisense), ); - std::fs::write(path, out).map_err(|e| Error::io(e, path))?; - Ok(()) + std::fs::write(path, out).map_err(|e| Error::io(e, path)) +} + +/// The `CellRanger.summary.csv` statistics, before formatting: the positional +/// funnel as raw fractions of all input reads. +#[derive(Clone, Copy, Debug)] +pub struct CellRangerSummary { + /// Reads in the input (all barcodes, mapped or not). + pub n_reads: u64, + pub frac_mapped_genome_unique: f64, + pub frac_exonic: f64, + pub frac_intronic: f64, + pub frac_intergenic: f64, + /// Uniquely-mapped reads landing on a gene's opposite strand. + pub frac_antisense: f64, } /// `features.tsv`: `gene_id gene_name "Gene Expression"` (CellRanger @@ -1923,6 +2137,61 @@ mod tests { assert!((pu0[&0] - 0.5).abs() < 1e-9 && (pu0[&1] - 0.5).abs() < 1e-9); } + #[cfg(feature = "anndata-out")] + #[test] + fn cells_to_csr_rows_are_whitelist_barcodes() { + let cells = vec![ + CellCounts { + cb: 1, + n_reads: 3, + entries: vec![(0, 2), (2, 1)], + }, + CellCounts { + cb: 3, + n_reads: 1, + entries: vec![(1, 1)], + }, + ]; + // 5 whitelist barcodes: rows 0, 2 and 4 have no counts at all. + let csr = cells_to_csr(&cells, 5, 3).expect("valid csr"); + assert_eq!((csr.nrows(), csr.ncols()), (5, 3)); + let t: Vec<_> = csr.triplet_iter().map(|(r, c, &v)| (r, c, v)).collect(); + assert_eq!(t, vec![(1, 0, 2), (1, 2, 1), (3, 1, 1)]); + } + + #[cfg(feature = "anndata-out")] + #[test] + fn multi_matrices_csr() { + // 4 whitelist barcodes × 3 genes. Cell 1: gene 0 = 4 unique. Cell 3: gene 2 = 1. + let unique = + CsrMatrix::try_from_csr_data(4, 3, vec![0, 0, 1, 1, 2], vec![0, 2], vec![4u64, 1]) + .expect("valid csr"); + // Cell 1: one ambiguous molecule (two records, same UMI) over {0,1}. + let mk = |cb, umi, genes: &[u32]| crate::solo::MultiGeneRecord { + cb, + umi, + genes: genes.to_vec(), + }; + let multi = vec![mk(1, 42, &[0, 1]), mk(1, 42, &[1])]; + + let methods = [MultiMethod::Uniform, MultiMethod::PropUnique]; + let out: Vec<_> = multi_matrices(&unique, &multi, &methods) + .collect::, _>>() + .expect("valid matrices"); + assert_eq!(out.len(), 2); + + // Uniform: cell 1 gets +0.5 on genes 0 and 1; cell 3 unchanged. + let (m, uni) = &out[0]; + assert_eq!(*m, MultiMethod::Uniform); + let t: Vec<_> = uni.triplet_iter().map(|(r, c, &v)| (r, c, v)).collect(); + assert_eq!(t, vec![(1, 0, 4.5), (1, 1, 0.5), (3, 2, 1.0)]); + + // PropUnique: all weight to gene 0 (gene 1 has no unique counts). + let (_, pu) = &out[1]; + let t: Vec<_> = pu.triplet_iter().map(|(r, c, &v)| (r, c, v)).collect(); + assert_eq!(t, vec![(1, 0, 5.0), (3, 2, 1.0)]); + } + #[test] fn called_cells_methods() { let mk = |cb, u| CellStat { diff --git a/src/solo/mod.rs b/src/solo/mod.rs index 2253dee7..411954b6 100644 --- a/src/solo/mod.rs +++ b/src/solo/mod.rs @@ -9,12 +9,14 @@ //! The barcode read is the SECOND `--readFilesIn` file (STAR convention: //! `--readFilesIn cDNA_read barcode_read`). It is never aligned — only parsed. +pub mod adata; pub mod count; pub mod gene; pub mod smartseq; pub mod whitelist; -pub use count::{UmiDedup, UmiFiltering, write_gene_matrix}; +pub use adata::OutputFormat; +pub use count::{UmiDedup, UmiFiltering, write_matrix_market}; pub use gene::{ GeneAssignment, Region, SoloFeature, SoloStrand, VelocytoCategory, assign_gene_se, classify_read, velocyto_category, diff --git a/tests/alignment_features.rs b/tests/alignment_features.rs index 2db7d39d..11d94c28 100644 --- a/tests/alignment_features.rs +++ b/tests/alignment_features.rs @@ -1117,6 +1117,182 @@ fn test_starsolo_summary_split() { ); } +// --------------------------------------------------------------------------- +// Test 9a2 — STARsolo --soloOutputFormat Zarr (MuData store) +// +// The same spliced reads as test 9b, but with every feature on and the counts +// written as one MuData store instead of MatrixMarket: a `gex` modality with +// Gene as X and GeneFull/spliced/unspliced/ambiguous as layers, a +// junction-indexed `sj` modality, and the MuData scaffolding around them. +// --------------------------------------------------------------------------- +#[test] +#[cfg(feature = "anndata-out")] +fn test_starsolo_output_format_zarr() { + let tmpdir = TempDir::new().unwrap(); + let genome = build_genome(); + let fasta = write_fasta(&tmpdir, &genome); + let gtf = write_gtf(&tmpdir); + let genome_dir = tmpdir.path().join("genome"); + build_index(&fasta, &genome_dir, "7", Some(>f)); + + let cdna_path = tmpdir.path().join("cdna.fq"); + let barcode_path = tmpdir.path().join("barcode.fq"); + let wl_path = tmpdir.path().join("whitelist.txt"); + let cb = "AAAACCCCGGGGTTTT"; + let umi = "ACGTACGTAC"; + // Spliced read: 25 bp from the end of Exon1 + 25 bp from the start of Exon2. + let mut spliced = genome[10025..10050].to_vec(); + spliced.extend_from_slice(&genome[10250..10275]); + { + let mut cf = fs::File::create(&cdna_path).unwrap(); + let mut bf = fs::File::create(&barcode_path).unwrap(); + for i in 0..6 { + writeln!(cf, "@r{i}").unwrap(); + cf.write_all(&spliced).unwrap(); + writeln!(cf, "\n+\n{}", "I".repeat(50)).unwrap(); + writeln!(bf, "@r{i}\n{cb}{umi}\n+\n{}", "I".repeat(26)).unwrap(); + } + let mut wf = fs::File::create(&wl_path).unwrap(); + writeln!(wf, "{cb}\nCCCCGGGGTTTTAAAA\nGGGGTTTTAAAACCCC").unwrap(); + } + + let output_dir = tmpdir.path().join("out_zarr"); + fs::create_dir_all(&output_dir).unwrap(); + let prefix = format!("{}/", output_dir.display()); + cargo_bin_cmd!("rustar-aligner") + .args([ + "--runMode", + "alignReads", + "--genomeDir", + genome_dir.to_str().unwrap(), + "--readFilesIn", + cdna_path.to_str().unwrap(), + barcode_path.to_str().unwrap(), + "--soloType", + "CB_UMI_Simple", + "--soloCBwhitelist", + wl_path.to_str().unwrap(), + "--soloFeatures", + "Gene", + "GeneFull", + "SJ", + "Velocyto", + "--soloStrand", + "Forward", + "--soloOutputFormat", + "Zarr", + "--sjdbGTFfile", + gtf.to_str().unwrap(), + "--outFileNamePrefix", + &prefix, + ]) + .assert() + .success(); + + // Zarr replaces MatrixMarket, so the per-feature .mtx directories are absent. + let solo = output_dir.join("Solo.out"); + let store = solo.join("matrix.zarr"); + assert!(store.is_dir(), "no MuData store at {}", store.display()); + assert!(!solo.join("Gene").join("raw").join("matrix.mtx").exists()); + + // MuData scaffolding: root + `mod` group metadata written by hand. + let root: serde_json::Value = + serde_json::from_str(&fs::read_to_string(store.join("zarr.json")).unwrap()).unwrap(); + assert_eq!(root["attributes"]["encoding-type"], "MuData"); + assert_eq!(root["node_type"], "group"); + let mods: serde_json::Value = + serde_json::from_str(&fs::read_to_string(store.join("mod/zarr.json")).unwrap()).unwrap(); + assert_eq!( + mods["attributes"]["mod-order"], + serde_json::json!(["gex", "sj"]) + ); + + // Both modalities are AnnData groups with the expected arrays. + for m in ["gex", "sj"] { + let g: serde_json::Value = serde_json::from_str( + &fs::read_to_string(store.join(format!("mod/{m}/zarr.json"))).unwrap(), + ) + .unwrap(); + assert_eq!(g["attributes"]["encoding-type"], "anndata", "modality {m}"); + for elem in ["obs", "var"] { + assert!( + store.join(format!("mod/{m}/{elem}")).exists(), + "modality {m} is missing {elem}" + ); + } + } + // No gene feature is privileged as X: every one of them is a named layer. + // (SJ has a single matrix, so it keeps X.) + assert!( + !store.join("mod/gex/X").exists(), + "gex must not have an X — the features are layers" + ); + assert!(store.join("mod/sj/X").exists(), "sj is missing X"); + for layer in ["Gene", "GeneFull", "spliced", "unspliced", "ambiguous"] { + assert!( + store.join(format!("mod/gex/layers/{layer}")).exists(), + "missing gex layer {layer}" + ); + } + for feature in ["Gene", "GeneFull"] { + assert!( + store.join(format!("mod/gex/obsm/stats_{feature}")).exists(), + "missing obsm/stats_{feature}" + ); + assert!( + store.join(format!("uns/summary/{feature}")).exists() + || store + .join(format!("uns/summary/{feature}/zarr.json")) + .exists(), + "missing uns/summary/{feature}" + ); + } + assert!(store.join("mod/sj/obsm/stats_SJ").exists()); + + // Every obsm frame is indexed by the barcodes — a polars DataFrame carries no + // index of its own, and Python AnnData rejects the frame if it does not match + // obs_names. + for (m, frame) in [ + ("gex", "stats_Gene"), + ("gex", "stats_GeneFull"), + ("sj", "stats_SJ"), + ] { + let (obs, obsm) = obs_and_obsm_index(&store.join("mod").join(m), frame); + assert!( + obs.len() == 3 && obs[0] == cb, + "unexpected obs_names: {obs:?}" + ); + assert_eq!( + obsm, obs, + "mod/{m}/obsm/{frame} is not indexed by obs_names" + ); + } +} + +/// `(obs_names, index of obsm/)` of one modality, read back from disk. +#[cfg(feature = "anndata-out")] +fn obs_and_obsm_index(modality: &std::path::Path, key: &str) -> (Vec, Vec) { + use anndata::Backend; + use anndata::backend::{DataContainer, GroupOp}; + use anndata::container::DataFrameElem; + use anndata_zarr::Zarr; + + let index = |c| { + DataFrameElem::::try_from(c) + .unwrap() + .inner() + .index + .clone() + .into_vec() + }; + let store = Zarr::open(modality).unwrap(); + let obsm = store.open_group("obsm").unwrap(); + ( + index(DataContainer::open(&store, "obs").unwrap()), + index(DataContainer::open(&obsm, key).unwrap()), + ) +} + // --------------------------------------------------------------------------- // Test 9b — STARsolo SJ (splice-junction) feature //