From 96b60e812fd67e57b018d604e63b0035d0f409fe Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:18:37 -0700 Subject: [PATCH 01/27] update and simplify dependencies --- Cargo.lock | 2021 +++++++++++++++++++++++++++++++++++++++------------- Cargo.toml | 24 +- 2 files changed, 1532 insertions(+), 513 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5954a64..b8c6ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,21 +13,20 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -71,9 +70,9 @@ dependencies = [ [[package]] name = "argminmax" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52424b59d69d69d5056d508b260553afd91c57e21849579cd1f50ee8b8b88eaa" +checksum = "70f13d10a41ac8d2ec79ee34178d61e6f47a29c2edfe7ef1721c7383b0359e65" dependencies = [ "num-traits", ] @@ -84,6 +83,30 @@ 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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[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-stream" version = "0.3.6" @@ -119,9 +142,12 @@ dependencies = [ [[package]] name = "atoi_simd" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4790f9e8961209112beb783d85449b508673cf4a6a419c8449b210743ac4dbe9" +checksum = "c2a49e05797ca52e312a0c658938b7d00693ef037799ef7187678f212d7684cf" +dependencies = [ + "debug_unsafe", +] [[package]] name = "atomic-waker" @@ -131,15 +157,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -147,7 +173,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -167,33 +193,52 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "boxcar" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa" + [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.9.3" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" dependencies = [ "proc-macro2", "quote", @@ -217,18 +262,18 @@ dependencies = [ [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.2.18" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -237,15 +282,21 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -256,25 +307,14 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "chrono-tz-build", "phf", ] -[[package]] -name = "chrono-tz-build" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" -dependencies = [ - "parse-zoneinfo", - "phf_codegen", -] - [[package]] name = "comfy-table" version = "7.1.4" @@ -301,6 +341,31 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -309,18 +374,18 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -368,7 +433,7 @@ dependencies = [ "bitflags", "crossterm_winapi", "parking_lot", - "rustix", + "rustix 0.38.44", "winapi", ] @@ -381,11 +446,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "debug_unsafe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d3cef41d236720ed453e102153a53e4cc3d2fde848c0078a50cf249e8e3e5b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "earcutr" @@ -393,7 +475,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" dependencies = [ - "itertools", + "itertools 0.11.0", "num-traits", ] @@ -402,21 +484,6 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn", -] [[package]] name = "equivalent" @@ -426,19 +493,40 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "ethnum" -version = "1.5.0" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] [[package]] name = "fallible-streaming-iterator" @@ -454,9 +542,9 @@ checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -468,12 +556,37 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" +[[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 = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix 1.0.8", + "windows-sys 0.59.0", +] + [[package]] name = "futures" version = "0.3.31" @@ -583,18 +696,18 @@ dependencies = [ [[package]] name = "geo-traits" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b018fc19fa58202b03f1c809aebe654f7d70fd3887dace34c3d05c11aeb474b5" +checksum = "2e7c353d12a704ccfab1ba8bfb1a7fe6cb18b665bf89d37f4f7890edcd260206" dependencies = [ "geo-types", ] [[package]] name = "geo-types" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb1950450d67efee2bbc5e429c68d052a822de3aad010d28b351fbb705224" +checksum = "75a4dcd69d35b2c87a7c83bce9af69fd65c9d68d3833a0ded568983928f3fc99" dependencies = [ "approx", "num-traits", @@ -605,36 +718,38 @@ dependencies = [ [[package]] name = "geographiclib-rs" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" dependencies = [ "libm", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -649,6 +764,25 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.3.1" @@ -672,9 +806,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -699,12 +833,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hex" version = "0.4.3" @@ -720,6 +848,113 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "i_float" version = "1.7.0" @@ -776,7 +1011,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core", ] [[package]] @@ -789,90 +1024,245 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.9.0" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "indoc" -version = "2.0.6" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "itertools" -version = "0.11.0" +name = "icu_normalizer" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ - "either", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "icu_normalizer_data" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] -name = "jobserver" -version = "0.1.33" +name = "icu_properties" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ - "getrandom 0.3.2", - "libc", + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", ] [[package]] -name = "js-sys" -version = "0.3.77" +name = "icu_properties_data" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] -name = "libc" -version = "0.2.171" +name = "icu_provider" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] [[package]] -name = "libm" -version = "0.2.11" +name = "idna" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] [[package]] -name = "libmimalloc-sys" -version = "0.1.42" +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "cc", - "libc", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", + "serde", +] + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -884,6 +1274,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[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" @@ -905,9 +1301,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "rawpointer", @@ -915,15 +1311,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -939,31 +1335,31 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -991,15 +1387,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -1028,30 +1415,21 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1061,9 +1439,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78" +checksum = "29f1dee9aa8d3f6f8e8b9af3803006101bb3653866ef056d530d53ae68587191" dependencies = [ "libc", "ndarray", @@ -1071,6 +1449,7 @@ dependencies = [ "num-integer", "num-traits", "pyo3", + "pyo3-build-config", "rustc-hash", ] @@ -1083,17 +1462,64 @@ dependencies = [ "memchr", ] +[[package]] +name = "object_store" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc4f07659e11cd45a341cd24d71e683e3be65d9ff1f8150061678fe60437496" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "http-body-util", + "humantime", + "hyper", + "itertools 0.14.0", + "parking_lot", + "percent-encoding", + "quick-xml", + "rand 0.9.2", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.12", + "tokio", + "tracing", + "url", + "walkdir", + "wasm-bindgen-futures", + "web-time", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[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.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1101,25 +1527,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] -name = "parse-zoneinfo" -version = "0.3.1" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1129,38 +1552,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ "phf_shared", - "rand", ] [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" dependencies = [ "siphasher", ] @@ -1185,20 +1588,21 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "planus" -version = "0.3.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1691dd09e82f428ce8d6310bd6d5da2557c82ff17694d2a32cad7242aea89f" +checksum = "3daf8e3d4b712abe1d690838f6e29fb76b76ea19589c4afa39ec30e12f62af71" dependencies = [ "array-init-cursor", + "hashbrown 0.15.4", ] [[package]] name = "polars" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72571dde488ecccbe799798bf99ab7308ebdb7cf5d95bcc498dbd5a132f0da4d" +checksum = "443824f43bca39b178353d6c09e4b44e115b21f107a5654d5f980d20b432a303" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "polars-arrow", "polars-core", "polars-error", @@ -1214,24 +1618,23 @@ dependencies = [ [[package]] name = "polars-arrow" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6611c758d52e799761cc25900666b71552e6c929d88052811bc9daad4b3321a8" +checksum = "809c5340e9e6c16eee5a07585161bae99f903f53af7402075efec23ee75fce5b" dependencies = [ - "ahash", "atoi_simd", + "bitflags", "bytemuck", "chrono", "chrono-tz", "dyn-clone", "either", "ethnum", - "getrandom 0.2.15", - "hashbrown 0.15.2", + "getrandom 0.2.16", + "hashbrown 0.15.4", "itoa", "lz4", "num-traits", - "parking_lot", "polars-arrow-format", "polars-error", "polars-schema", @@ -1239,7 +1642,6 @@ dependencies = [ "serde", "simdutf8", "streaming-iterator", - "strength_reduce", "strum_macros", "version_check", "zstd", @@ -1247,9 +1649,9 @@ dependencies = [ [[package]] name = "polars-arrow-format" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b0ef2474af9396b19025b189d96e992311e6a47f90c53cd998b36c4c64b84c" +checksum = "863c04c514be005eced7db7053e20d49f7e7a58048a282fa52dfea1fd5434e78" dependencies = [ "planus", "serde", @@ -1257,85 +1659,89 @@ dependencies = [ [[package]] name = "polars-compute" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f2547dbb27599a8ffe68e56159f5996ba03d1dad0382ccb62c109ceacdeb6" +checksum = "8b8802ff2cccea01a845ea8267a7600e495747ed109035bb5020c33eb8717ff4" dependencies = [ "atoi_simd", "bytemuck", "chrono", "either", "fast-float2", + "hashbrown 0.15.4", "itoa", "num-traits", "polars-arrow", "polars-error", "polars-utils", + "rand 0.8.5", "ryu", + "serde", + "skiplist", "strength_reduce", + "strum_macros", "version_check", ] [[package]] name = "polars-core" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796d06eae7e6e74ed28ea54a8fccc584ebac84e6cf0e1e9ba41ffc807b169a01" +checksum = "3fc3c99d7000be1be11665e1e260b93dc3b927342b9da3b53d9a1ac264e4343d" dependencies = [ - "ahash", "bitflags", + "boxcar", "bytemuck", "chrono", "chrono-tz", "comfy-table", "either", "hashbrown 0.14.5", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "indexmap", "itoa", "num-traits", - "once_cell", "polars-arrow", "polars-compute", "polars-error", "polars-row", "polars-schema", "polars-utils", - "rand", + "rand 0.8.5", "rand_distr", "rayon", "regex", "serde", "serde_json", "strum_macros", - "thiserror 2.0.12", + "uuid", "version_check", "xxhash-rust", ] [[package]] name = "polars-error" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d6529cae0d1db5ed690e47de41fac9b35ae0c26d476830c2079f130887b847" +checksum = "1397c17712e61a55fdd45c033a69f0451fde2973ff2609c22e363e21d68f11ef" dependencies = [ + "object_store", + "parking_lot", "polars-arrow-format", "regex", + "signal-hook", "simdutf8", - "thiserror 2.0.12", ] [[package]] name = "polars-expr" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e639991a8ad4fb12880ab44bcc3cf44a5703df003142334d9caf86d77d77e7" +checksum = "33d3aa6722c9a3e0b721ec2bcdc4affd9e50e4cb606cd81bb94535a9a5a6ade9" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "num-traits", - "once_cell", "polars-arrow", "polars-compute", "polars-core", @@ -1345,31 +1751,33 @@ dependencies = [ "polars-row", "polars-time", "polars-utils", - "rand", + "rand 0.8.5", "rayon", + "recursive", ] [[package]] name = "polars-io" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719a77e94480f6be090512da196e378cbcbeb3584c6fe1134c600aee906e38ab" +checksum = "1a632d442a99821250a8fa66f7d488bf5ee98e5f515e65256b12956cb81fc110" dependencies = [ - "ahash", "async-trait", "atoi_simd", + "blake3", "bytes", "chrono", "fast-float2", + "fs4", "futures", "glob", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "home", "itoa", "memchr", "memmap2", "num-traits", - "once_cell", + "object_store", "percent-encoding", "polars-arrow", "polars-core", @@ -1378,48 +1786,48 @@ dependencies = [ "polars-schema", "polars-time", "polars-utils", - "pyo3", "rayon", "regex", + "reqwest", "ryu", "serde", + "serde_json", "simdutf8", "tokio", "tokio-util", + "url", ] [[package]] name = "polars-lazy" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a731a672dfc8ac38c1f73c9a4b2ae38d2fc8ac363bfb64c5f3a3e072ffc5ad" +checksum = "f4ed0c87bdc8820447a38ae8efdb5a51a5a93e8bd528cffb05d05cf1145e4161" dependencies = [ - "ahash", "bitflags", "chrono", + "either", "memchr", - "once_cell", "polars-arrow", + "polars-compute", "polars-core", "polars-expr", "polars-io", "polars-mem-engine", "polars-ops", - "polars-pipe", "polars-plan", "polars-stream", "polars-time", "polars-utils", - "pyo3", "rayon", "version_check", ] [[package]] name = "polars-mem-engine" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33442189bcbf2e2559aa7914db3835429030a13f4f18e43af5fba9d1b018cf12" +checksum = "675294ddf9174029e48caa4e59b0665ea64bfb784a366b197690895a6ed65c68" dependencies = [ "memmap2", "polars-arrow", @@ -1431,29 +1839,28 @@ dependencies = [ "polars-plan", "polars-time", "polars-utils", - "pyo3", "rayon", + "recursive", ] [[package]] name = "polars-ops" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb83218b0c216104f0076cd1a005128be078f958125f3d59b094ee73d78c18e" +checksum = "1eb4db68956f857c52eeda072d87644a7b42eac41d55073af94dfac8441af6cf" dependencies = [ - "ahash", "argminmax", "base64", "bytemuck", "chrono", "chrono-tz", "either", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "hex", "indexmap", + "libm", "memchr", "num-traits", - "once_cell", "polars-arrow", "polars-compute", "polars-core", @@ -1463,7 +1870,6 @@ dependencies = [ "rayon", "regex", "regex-syntax", - "serde", "strum_macros", "unicode-normalization", "unicode-reverse", @@ -1472,17 +1878,16 @@ dependencies = [ [[package]] name = "polars-parquet" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c60ee85535590a38db6c703a21be4cb25342e40f573f070d1e16f9d84a53ac7" +checksum = "7c849c10edd9511ccd4ec4130e283ee3a8b3bb48a7d74ac6354c1c20add81065" dependencies = [ - "ahash", "async-stream", "base64", "bytemuck", "ethnum", "futures", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "num-traits", "polars-arrow", "polars-compute", @@ -1504,49 +1909,21 @@ dependencies = [ "futures", ] -[[package]] -name = "polars-pipe" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d238fb76698f56e51ddfa89b135e4eda56a4767c6e8859eed0ab78386fcd52" -dependencies = [ - "crossbeam-channel", - "crossbeam-queue", - "enum_dispatch", - "hashbrown 0.15.2", - "num-traits", - "once_cell", - "polars-arrow", - "polars-compute", - "polars-core", - "polars-expr", - "polars-io", - "polars-ops", - "polars-plan", - "polars-row", - "polars-utils", - "rayon", - "uuid", - "version_check", -] - [[package]] name = "polars-plan" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f03533a93aa66127fcb909a87153a3c7cfee6f0ae59f497e73d7736208da54c" +checksum = "71fb4412c42bf637c2c02a617381c682ed425d9c8e4bd1fcb85cf352ed2a67c6" dependencies = [ - "ahash", "bitflags", "bytemuck", "bytes", "chrono", "chrono-tz", "either", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "memmap2", "num-traits", - "once_cell", "percent-encoding", "polars-arrow", "polars-compute", @@ -1555,20 +1932,18 @@ dependencies = [ "polars-ops", "polars-time", "polars-utils", - "pyo3", "rayon", "recursive", "regex", - "serde", "strum_macros", "version_check", ] [[package]] name = "polars-row" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf47f7409f8e75328d7d034be390842924eb276716d0458607be0bddb8cc839" +checksum = "08fb77ac1d37340d9cfe57cf58000cf3d9cce429e10d25066952c6145c684cc0" dependencies = [ "bitflags", "bytemuck", @@ -1580,9 +1955,9 @@ dependencies = [ [[package]] name = "polars-schema" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416621ae82b84466cf4ff36838a9b0aeb4a67e76bd3065edc8c9cb7da19b1bc7" +checksum = "ada7c7e2fbbeffbdd67628cd8a89f02b0a8d21c71d34e297e2463a7c17575203" dependencies = [ "indexmap", "polars-error", @@ -1593,10 +1968,11 @@ dependencies = [ [[package]] name = "polars-sql" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edaab553b90aa4d6743bb538978e1982368acb58a94408d7dd3299cad49c7083" +checksum = "4a8e512b1f05ffda9963fe8f6a7c62dcba86be85218bc033ecdad2802cc1b1a0" dependencies = [ + "bitflags", "hex", "polars-core", "polars-error", @@ -1605,7 +1981,7 @@ dependencies = [ "polars-plan", "polars-time", "polars-utils", - "rand", + "rand 0.8.5", "regex", "serde", "sqlparser", @@ -1613,17 +1989,24 @@ dependencies = [ [[package]] name = "polars-stream" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498997b656c779610c1496b3d96a59fe569ef22a5b81ccfe5325cb3df8dff2fd" +checksum = "5b0a02d8050acd9b64ed7e36c5bc96f6d4f46a940220f9c0e34c96b51f830f8c" dependencies = [ + "async-channel", + "async-trait", "atomic-waker", + "bitflags", + "crossbeam-channel", "crossbeam-deque", + "crossbeam-queue", "crossbeam-utils", "futures", "memmap2", "parking_lot", + "percent-encoding", "pin-project-lite", + "polars-arrow", "polars-core", "polars-error", "polars-expr", @@ -1633,7 +2016,7 @@ dependencies = [ "polars-parquet", "polars-plan", "polars-utils", - "rand", + "rand 0.8.5", "rayon", "recursive", "slotmap", @@ -1643,9 +2026,9 @@ dependencies = [ [[package]] name = "polars-time" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d192efbdab516d28b3fab1709a969e3385bd5cda050b7c9aa9e2502a01fda879" +checksum = "72e84a30110880ffede8d93c085fc429ab1b8bf1acf3d6d489143dd34be374c4" dependencies = [ "atoi_simd", "bytemuck", @@ -1653,7 +2036,6 @@ dependencies = [ "chrono-tz", "now", "num-traits", - "once_cell", "polars-arrow", "polars-compute", "polars-core", @@ -1662,45 +2044,44 @@ dependencies = [ "polars-utils", "rayon", "regex", - "serde", "strum_macros", ] [[package]] name = "polars-utils" -version = "0.46.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f6c8166a4a7fbc15b87c81645ed9e1f0651ff2e8c96cafc40ac5bf43441a10" +checksum = "a05e033960552c47fc35afe14d5af5b29696acc97ae5d3c585ebc33c246cc15f" dependencies = [ - "ahash", "bincode", "bytemuck", "bytes", "compact_str", "flate2", - "hashbrown 0.15.2", + "foldhash", + "hashbrown 0.15.4", "indexmap", "libc", "memmap2", "num-traits", - "once_cell", "polars-error", - "pyo3", - "rand", + "rand 0.8.5", "raw-cpuid", "rayon", + "regex", + "rmp-serde", "serde", "serde_json", + "slotmap", "stacker", - "sysinfo", "version_check", ] [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -1711,13 +2092,22 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -1731,29 +2121,28 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" dependencies = [ "cc", ] [[package]] name = "pyo3" -version = "0.23.5" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -1767,9 +2156,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.5" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598" dependencies = [ "once_cell", "python3-dll-a", @@ -1778,9 +2167,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.5" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c" dependencies = [ "libc", "pyo3-build-config", @@ -1788,9 +2177,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.23.5" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1800,9 +2189,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.5" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc" dependencies = [ "heck", "proc-macro2", @@ -1813,31 +2202,93 @@ dependencies = [ [[package]] name = "pyo3-polars" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7db078e647fbd863d7605d13ef1553c06dd84ba9559f76512d3ca63d5fff20" +checksum = "780638e429de4b5c2b336c3f2527440a55be1b7e8627579f6e98f8e189192c7b" dependencies = [ "libc", "once_cell", "polars", "polars-arrow", "polars-core", - "polars-lazy", - "polars-plan", - "polars-utils", "pyo3", "thiserror 1.0.69", ] [[package]] name = "python3-dll-a" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fe4227a288cf9493942ad0220ea3f185f4d1f2a14f197f7344d6d02f4ed4ed" +checksum = "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8" dependencies = [ "cc", ] +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -1849,9 +2300,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1860,8 +2311,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1871,7 +2332,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[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.3", ] [[package]] @@ -1880,7 +2351,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -1890,7 +2370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -1950,9 +2430,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -1986,11 +2466,89 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "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 = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "robust" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] name = "rstar" @@ -2005,9 +2563,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -2017,14 +2575,14 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rusterize" -version = "0.3.0" +version = "0.4.0" dependencies = [ "geo", "geo-traits", "geo-types", "mimalloc", "ndarray", - "num_cpus", + "num-traits", "numpy", "polars", "pyo3", @@ -2043,15 +2601,75 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -2059,12 +2677,53 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.219" @@ -2087,9 +2746,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -2097,12 +2756,43 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -2116,14 +2806,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] -name = "slab" -version = "0.4.9" +name = "skiplist" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "0eec25f46463fcdc5e02f388c2780b1b58e01be81a8378e62ec60931beccc3f6" dependencies = [ - "autocfg", + "rand 0.8.5", ] +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + [[package]] name = "slotmap" version = "1.0.7" @@ -2135,27 +2831,37 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spade" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" +checksum = "a14e31a007e9f85c32784b04f89e6e194bb252a4d41b4a8ccd9e77245d901c8c" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", "num-traits", "robust", "smallvec", @@ -2178,9 +2884,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" +checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" dependencies = [ "cc", "cfg-if", @@ -2229,11 +2935,17 @@ dependencies = [ "syn", ] +[[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.100" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2241,23 +2953,30 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.33.1" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "windows", + "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", ] [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "thiserror" @@ -2319,6 +3038,16 @@ dependencies = [ "tikv-jemalloc-sys", ] +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.9.0" @@ -2336,24 +3065,48 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", - "socket2", - "windows-sys 0.52.0", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", ] [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -2364,21 +3117,103 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +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 = "unicode-ident" version = "1.0.18" @@ -2411,9 +3246,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unindent" @@ -2421,13 +3256,38 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2436,11 +3296,30 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[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.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -2477,6 +3356,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -2509,6 +3401,39 @@ dependencies = [ "unicode-ident", ] +[[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 = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +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 = "winapi" version = "0.3.9" @@ -2526,57 +3451,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.57.0" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-core 0.57.0", - "windows-targets", + "windows-sys 0.59.0", ] [[package]] -name = "windows-core" -version = "0.57.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.3.2", + "windows-result", "windows-strings", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -2588,17 +3489,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -2612,33 +3502,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-result" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -2649,7 +3530,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2658,7 +3539,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -2667,14 +3557,31 @@ 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", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -2683,53 +3590,101 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -2745,9 +3700,9 @@ dependencies = [ [[package]] name = "wkb" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e2c084338d6407d24c5a43208aca32128a5d62107eab5ca18314395c4aa3f0" +checksum = "ff9eff6aebac4c64f9c7c057a68f6359284e2a80acf102dffe041fe219b3a082" dependencies = [ "byteorder", "geo-traits", @@ -2755,6 +3710,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "xxhash-rust" version = "0.8.15" @@ -2762,28 +3723,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] -name = "zerocopy" -version = "0.7.35" +name = "yoke" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ - "zerocopy-derive 0.7.35", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "zerocopy-derive 0.8.24", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -2791,10 +3767,59 @@ dependencies = [ ] [[package]] -name = "zerocopy-derive" -version = "0.8.24" +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index ef51f8e..38efd9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ cargo-features = ["profile-rustflags"] [package] name = "rusterize" -version = "0.3.0" +version = "0.4.0" edition = "2024" resolver = "2" @@ -12,16 +12,16 @@ crate-type = ["cdylib"] [dependencies] geo = "0.30.0" -geo-traits = "0.2.0" -geo-types = "0.7.16" +geo-traits = "0.3.0" +geo-types = "0.7.17" ndarray = { version = "0.16.1", features = ["rayon"] } -num_cpus = "1.16.0" -numpy = "0.23.0" -polars = { version = "0.46.0", features = ["lazy", "simd", "performant", "nightly"] } -pyo3 = { version = "0.23.5", features = ["extension-module", "abi3-py311", "generate-import-lib"] } -pyo3-polars = { version = "0.20.0", features = ["lazy", "dtype-struct"] } +num-traits = "0.2.19" +numpy = "0.25.0" +polars = { version = "0.49.1", features = ["lazy", "simd", "performant", "nightly"] } +pyo3 = { version = "0.25.1", features = ["extension-module", "abi3-py311", "generate-import-lib"] } +pyo3-polars = "0.22.0" rayon = "1.10.0" -wkb = "0.8.0" +wkb = "0.9.0" # OS-specific allocators [target.'cfg(not(target_family = "unix"))'.dependencies] @@ -33,12 +33,6 @@ tikv-jemallocator = { version = "*", features = ["disable_initial_exec_tls", "ba [target.'cfg(all(target_family = "unix", target_os = "macos"))'.dependencies] tikv-jemallocator = { version = "*", features = ["disable_initial_exec_tls"] } -[profile.profiler] -inherits = "dev" -debug = true -lto = true -codegen-units = 1 - [profile.dist-release] inherits = "release" rustflags = ["-C", "target-feature=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe"] From 50c2b892237ac68db5a72aa68afc0aca10caaad6 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:19:19 -0700 Subject: [PATCH 02/27] simplify import and refine checking logics --- python/rusterize/__init__.py | 115 ++++++++++++++++++++++++++++++++++- python/rusterize/core.py | 107 -------------------------------- 2 files changed, 114 insertions(+), 108 deletions(-) delete mode 100644 python/rusterize/core.py diff --git a/python/rusterize/__init__.py b/python/rusterize/__init__.py index df056f1..43a6ea8 100644 --- a/python/rusterize/__init__.py +++ b/python/rusterize/__init__.py @@ -1,4 +1,117 @@ +from __future__ import annotations import importlib.metadata -from .core import * + +from types import NoneType +from typing import List, Tuple + +import numpy as np +import polars as pl +from geopandas import GeoDataFrame +import rioxarray +from xarray import DataArray +from .rusterize import _rusterize __version__ = importlib.metadata.version("rusterize") + + +def rusterize( + gdf: GeoDataFrame, + res: Tuple | List | np.ndarray | None = None, + out_shape: Tuple | List | np.ndarray | None = None, + extent: Tuple | List | np.ndarray = None, + field: str | None = None, + by: str | None = None, + burn: int | float | None = None, + fun: str = "last", + background: int | float | None = np.nan, + dtype: str = "float64", +) -> DataArray: + """ + Fast geopandas rasterization into xarray.DataArray + + Args: + :param gdf: geopandas dataframe to rasterize. + :param res: (xres, yres) for rasterized data. + :param out_shape: (nrows, ncols) for regularized output shape. + :param extent: (xmin, xmax, ymin, ymax) for regularized extent. + :param field: field to rasterize, mutually exclusive with `burn`. Default is None. + :param by: column to rasterize, assigns each unique value to a layer in the stack based on field. Default is None. + :param burn: burn a value onto the raster, mutually exclusive with `field`. Default is None. + :param fun: pixel function to use. Available options are `sum`, `first`, `last`, `min`, `max`, `count`, or `any`. Default is `last`. + :param background: background value in final raster. Default is np.nan. + :param dtype: specify the output dtype. Default is `float64`. + + Returns: + Rasterized xarray.DataArray. + + Notes: + When any of `res`, `out_shape`, or `extent` is not provided, it is inferred from the other arguments when applicable. + Unless `extent` is specified, a half-pixel buffer is applied to avoid missing points on the border. + The logics dictating the final spatial properties of the rasterized geometries follow those of GDAL. + + If `field` is not in `gdf`, then a default `burn` value of 1 is rasterized. + + A `None` value for `dtype` corresponds to the default of that dtype. An illegal value for a dtype will be replaced with the default of + that dtype. For example, a `background=np.nan` for `dtype="uint8"` will become `background=0`, where `0` is the default for `uint8`. + """ + # type checks + if not isinstance(gdf, GeoDataFrame): + raise TypeError("`gdf` must be a geopandas dataframe.") + if not isinstance(res, (tuple, list, np.ndarray, NoneType)): + raise TypeError("`resolution` must be a tuple, list, or numpy array of (x, y).") + if not isinstance(out_shape, (tuple, list, np.ndarray, NoneType)): + raise TypeError("`out_shape` must be a tuple, list, or numpy array of (nrows, ncols).") + if not isinstance(extent, (tuple, list, np.ndarray, NoneType)): + raise TypeError("`extent` must be a tuple, list, or numpy array of (xmin, ymin, xmax, ymax).") + if not isinstance(field, (str, NoneType)): + raise TypeError("`field` must be a string column name.") + if not isinstance(by, (str, NoneType)): + raise TypeError("`by` must be a string column name.") + if not isinstance(burn, (int, float, NoneType)): + raise TypeError("`burn` must be an integer or float.") + if not isinstance(fun, str): + raise TypeError("`pixel_fn` must be one of sum, first, last, min, max, count, or any.") + if not isinstance(background, (int, float, NoneType)): + raise TypeError("`background` must be integer, float, or None.") + if not isinstance(dtype, str): + raise TypeError("`dtype` must be a one of uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64") + + # value checks + if not res and not out_shape and not extent: + raise ValueError("One of `res`, `out_shape`, or `extent` must be provided.") + if extent and not res and not out_shape: + raise ValueError("Must also specify `res` or `out_shape` with extent.") + if res and (len(res) != 2 or any(r <= 0 for r in res) or any(not isinstance(r, (int, float)) for r in res)): + raise ValueError("Resolution must be 2 positive numbers.") + if out_shape and (len(out_shape) != 2 or any(s <= 0 for s in out_shape) or any(not isinstance(s, int) for s in out_shape)): + raise ValueError("Output shape must be 2 positive integers.") + if extent and len(extent) != 4: + raise ValueError("Extent must be 4 numbers (xmin, ymin, xmax, ymax).") + if field and burn: + raise ValueError("Only one of `field` or `burn` can be specified.") + + # defaults + _res = res if res else (0, 0) + _shape = out_shape if out_shape else (0, 0) + (_bounds, _has_extent) = (extent, True) if extent else (gdf.total_bounds, False) + + # RasterInfo + raster_info = { + "nrows": _shape[0], + "ncols": _shape[1], + "xmin": _bounds[0], + "ymin": _bounds[1], + "xmax": _bounds[2], + "ymax": _bounds[3], + "xres": _res[0], + "yres": _res[1], + "has_extent": _has_extent, + } + + # extract columns of interest and convert to polars + cols = list(set([col for col in (field, by) if col])) + df = pl.from_pandas(gdf[cols]) if cols else None + + # rusterize + r = _rusterize(gdf.geometry, raster_info, fun, df, field, by, burn, background, dtype) + return DataArray.from_dict(r).rio.write_crs(gdf.crs, inplace=True) diff --git a/python/rusterize/core.py b/python/rusterize/core.py deleted file mode 100644 index a504abb..0000000 --- a/python/rusterize/core.py +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import annotations - -from types import NoneType -from typing import Any, Dict, Tuple - -import polars as pl -from geopandas import GeoDataFrame -import rioxarray -from xarray import DataArray -from .rusterize import _rusterize - - -def rusterize(gdf: GeoDataFrame, - res: Tuple[int, ...] | Tuple[float, ...] | None = None, - out_shape: Tuple[int, ...] | None = None, - extent: Tuple[int, ...] | Tuple[float, ...] | None = None, - field: str | None = None, - by: str | None = None, - fun: str = "last", - background: int | float | None = None, - ) -> Dict[str, Any]: - """ - Fast geopandas rasterization into xarray.DataArray - - Args: - :param gdf: geopandas dataframe to rasterize. - :param res: tuple of (xres, yres) for rasterized data. - :param out_shape: tuple of (nrows, ncols) for regularized output shape. - :param extent: tuple of (xmin, xmax, ymin, ymax) for regularized extent. - :param field: field to rasterize. Default is None. - :param by: column to rasterize, assigns each unique value to a layer in the stack based on field. Default is None. - :param fun: pixel function to use. Available options are `sum`, `first`, `last`, `min`, `max`, `count`, or `any`. Default is `last`. - :param background: background value in final raster. Default is None (NaN). - - Returns: - Rasterized xarray.DataArray. - - Note: - When any of `res`, `out_shape`, or `extent` is not provided, it is inferred from the other arguments when applicable. - Unless `extent` is specified, a half-pixel buffer is applied to avoid missing points on the border. - The logics dictating the final spatial properties of the rasterized geometries follow those of GDAL. - """ - # type checks - if not isinstance(gdf, GeoDataFrame): - raise TypeError("Must pass a valid geopandas dataframe.") - if not isinstance(res, (tuple, NoneType)): - raise TypeError("Must pass a valid resolution tuple (x, y).") - if not isinstance(out_shape, (tuple, NoneType)): - raise TypeError("Must pass a valid output shape tuple (nrows, ncols).") - if not isinstance(extent, (tuple, NoneType)): - raise TypeError("Must pass a valid extent tuple (xmin, ymin, xmax, ymax).") - if not isinstance(field, (str, NoneType)): - raise TypeError("Must pass a valid string to field.") - if not isinstance(by, (str, NoneType)): - raise TypeError("Must pass a valid string to by.") - if not isinstance(fun, str): - raise TypeError("Must pass a valid string to pixel_fn. Select one of sum, first, last, min, max, count, or any.") - if not isinstance(background, (int, float, NoneType)): - raise TypeError("Must pass a valid background type.") - - # value checks - if not res and not out_shape and not extent: - raise ValueError("One of `res`, `out_shape`, or `extent` must be provided.") - if extent and not res and not out_shape: - raise ValueError("Must also specify `res` or `out_shape` with extent.") - if res and (len(res) != 2 or any(r <= 0 for r in res) or any(not isinstance(r, (int, float)) for r in res)): - raise ValueError("Resolution must be 2 positive numbers.") - if out_shape and (len(out_shape) != 2 or any(s <= 0 for s in out_shape) or any(not isinstance(s, int) for s in out_shape)): - raise ValueError("Output shape must be 2 positive integers.") - if extent and len(extent) != 4: - raise ValueError("Extent must be 4 numbers (xmin, ymin, xmax, ymax).") - if by and not field: - raise ValueError("If `by` is specified, `field` must also be specified.") - - # defaults - _res = res if res else (0, 0) - _shape = out_shape if out_shape else (0, 0) - (_bounds, has_extent) = (extent, True) if extent else (gdf.total_bounds, False) - - # RasterInfo - raster_info = { - "nrows": _shape[0], - "ncols": _shape[1], - "xmin": _bounds[0], - "ymin": _bounds[1], - "xmax": _bounds[2], - "ymax": _bounds[3], - "xres": _res[0], - "yres": _res[1], - "has_extent": has_extent - } - - # extract columns of interest and convert to polars - cols = list(set([col for col in (field, by) if col])) - df = pl.from_pandas(gdf[cols]) if cols else None - - # rusterize - r = _rusterize( - gdf.geometry, - raster_info, - fun, - df, - field, - by, - background - ) - return DataArray.from_dict(r).rio.write_crs(gdf.crs, inplace=True) \ No newline at end of file From 8bf638a8c02d377aefc3d5a6f26e58d9409bff70 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:21:18 -0700 Subject: [PATCH 03/27] handle GeometryCollection with enhanced EdgeCollection enum --- src/edge_collection.rs | 79 +++++++++++++++++++++++------------------- src/structs/edge.rs | 42 ++++++++++++++++++++-- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/edge_collection.rs b/src/edge_collection.rs index 1e5a35d..885d7e4 100644 --- a/src/edge_collection.rs +++ b/src/edge_collection.rs @@ -1,60 +1,67 @@ /* Build structured edge collection from a single (multi)polygon or (multi)linestring. -If multi, then iterates over each inner geometry. +If multi or GeometryCollection, then iterates over each inner geometry. From the Geometry, the values are extracted and reconstructed as an array of nodes. */ -use crate::structs::edge::{EdgeCollection, LineEdge, PolyEdge}; -use crate::structs::raster::RasterInfo; +use crate::structs::{ + edge::{EdgeCollection, LineEdge, PolyEdge}, + raster::RasterInfo, +}; use geo::prelude::*; use geo_types::{Geometry, LineString}; use numpy::ndarray::Array2; pub fn build_edges(geom: &Geometry, raster_info: &RasterInfo) -> EdgeCollection { - match geom { - // polygon - Geometry::Polygon(polygon) => { - let mut polyedges: Vec = Vec::new(); - // handle exterior polygon - process_ring(&mut polyedges, polygon.exterior(), raster_info); - // handle interior polygons (if any) - for hole in polygon.interiors() { - process_ring(&mut polyedges, hole, raster_info); + let mut edges = EdgeCollection::Empty; + let mut stack = vec![geom]; + + while let Some(current_geom) = stack.pop() { + match current_geom { + Geometry::GeometryCollection(collection) => { + // push geometries back to stack + for inner in collection { + stack.push(inner); + } } - EdgeCollection::PolyEdges(polyedges) - } - // multipolygon - iterate over each inner polygon - Geometry::MultiPolygon(multipolygon) => { - let mut polyedges: Vec = Vec::new(); - for polygon in multipolygon { - // handle exterior polygon + Geometry::Polygon(polygon) => { + let mut polyedges: Vec = Vec::new(); process_ring(&mut polyedges, polygon.exterior(), raster_info); - // handle interior polygons (if any) + // process holes in geometry for hole in polygon.interiors() { process_ring(&mut polyedges, hole, raster_info); } + edges.add_polyedges(polyedges); } - EdgeCollection::PolyEdges(polyedges) - } - // linestring - Geometry::LineString(line) => { - let mut linedges: Vec = Vec::new(); - // handle single segment - process_line(&mut linedges, line, raster_info); - EdgeCollection::LineEdges(linedges) - } - // multilinestring - iterate over each inner linestring - Geometry::MultiLineString(multiline) => { - let mut linedges: Vec = Vec::new(); - // handle multiple segments - for line in multiline { + Geometry::MultiPolygon(multipolygon) => { + let mut polyedges: Vec = Vec::new(); + for polygon in multipolygon { + process_ring(&mut polyedges, polygon.exterior(), raster_info); + // process holes in geometry + for hole in polygon.interiors() { + process_ring(&mut polyedges, hole, raster_info); + } + } + edges.add_polyedges(polyedges); + } + Geometry::LineString(line) => { + let mut linedges: Vec = Vec::new(); process_line(&mut linedges, line, raster_info); + edges.add_linedges(linedges); } - EdgeCollection::LineEdges(linedges) + Geometry::MultiLineString(multiline) => { + let mut linedges: Vec = Vec::new(); + for line in multiline { + process_line(&mut linedges, line, raster_info); + } + edges.add_linedges(linedges); + } + _ => (), } - _ => unimplemented!("Unsupported geometry type."), } + + edges } fn build_node_array(line: &LineString) -> Array2 { diff --git a/src/structs/edge.rs b/src/structs/edge.rs index 5ca25c9..8e9fb72 100644 --- a/src/structs/edge.rs +++ b/src/structs/edge.rs @@ -7,15 +7,51 @@ use std::cmp::Ordering; // collection of edges pub enum EdgeCollection { + Empty, PolyEdges(Vec), LineEdges(Vec), + Mixed { + polyedges: Vec, + linedges: Vec, + }, } impl EdgeCollection { - pub fn is_empty(&self) -> bool { + pub fn add_polyedges(&mut self, new_polyedges: Vec) { + if new_polyedges.is_empty() { + return; + } + match self { + EdgeCollection::Empty => *self = EdgeCollection::PolyEdges(new_polyedges), + EdgeCollection::PolyEdges(polyedges) => polyedges.extend(new_polyedges), + EdgeCollection::LineEdges(linedges) => { + *self = { + EdgeCollection::Mixed { + polyedges: new_polyedges, + linedges: std::mem::take(linedges), + } + } + } + EdgeCollection::Mixed { polyedges, .. } => polyedges.extend(new_polyedges), + } + } + + pub fn add_linedges(&mut self, new_linedges: Vec) { + if new_linedges.is_empty() { + return; + } match self { - EdgeCollection::PolyEdges(poly_edges) => poly_edges.is_empty(), - EdgeCollection::LineEdges(line_edges) => line_edges.is_empty(), + EdgeCollection::Empty => *self = EdgeCollection::LineEdges(new_linedges), + EdgeCollection::PolyEdges(polyedges) => { + *self = { + EdgeCollection::Mixed { + polyedges: std::mem::take(polyedges), + linedges: new_linedges, + } + } + } + EdgeCollection::LineEdges(linedges) => linedges.extend(new_linedges), + EdgeCollection::Mixed { linedges, .. } => linedges.extend(new_linedges), } } } From 13319e9487c9e8362bbaf80547feccbb2776a8b6 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:21:55 -0700 Subject: [PATCH 04/27] decoupled polygon and line rasterization to handle GeometryCollection --- src/rasterize.rs | 114 ---------------------------- src/rasterize_geometry.rs | 153 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 114 deletions(-) delete mode 100644 src/rasterize.rs create mode 100644 src/rasterize_geometry.rs diff --git a/src/rasterize.rs b/src/rasterize.rs deleted file mode 100644 index 4a66f79..0000000 --- a/src/rasterize.rs +++ /dev/null @@ -1,114 +0,0 @@ -/* -Rasterize a single (multi)polygon or (multi)linestring. - */ - -use crate::edge_collection; -use crate::pixel_functions::PixelFn; -use crate::structs::{ - edge::{EdgeCollection, PolyEdge, less_by_x, less_by_ystart}, - raster::RasterInfo, -}; - -use edge_collection::build_edges; -use geo_types::Geometry; -use numpy::ndarray::ArrayViewMut2; -use rayon::prelude::*; - -pub fn rasterize( - raster_info: &RasterInfo, - geom: &Geometry, - field_value: &f64, - ndarray: &mut ArrayViewMut2, - pxfn: &PixelFn, - background: &f64, -) { - // build edge collection - let edges = build_edges(geom, raster_info); - - // early return if no edges - if edges.is_empty() { - return; - } - - // branch by geometry type - match edges { - EdgeCollection::PolyEdges(mut polyedges) => { - // sort edges - polyedges.par_sort_by(less_by_ystart); - - // start with first y line - let mut yline = polyedges.first().unwrap().ystart; - - // active edges - let mut active_edges: Vec = Vec::new(); - - // rasterize loop - let ncols = raster_info.ncols as f64; - while yline < raster_info.nrows && !(active_edges.is_empty() && polyedges.is_empty()) { - // transfer current edges to active edges - active_edges.extend( - polyedges.extract_if(.., |edge| edge.ystart <= yline), // experimental - ); - // sort active edges - active_edges.par_sort_by(less_by_x); - - // even-odd polygon fill - for (edge1, edge2) in active_edges - .iter() - .zip(active_edges.iter().skip(1)) - .step_by(2) - { - // clamp the x-coordinates of the edges - let xstart = edge1.x.clamp(0.0, ncols).ceil() as usize; - let xend = edge2.x.clamp(0.0, ncols).ceil() as usize; - - // fill the pixels between xstart and xend - for xpix in xstart..xend { - pxfn(ndarray, yline, xpix, field_value, background); - } - } - yline += 1; - - active_edges.retain_mut(|edge| { - if edge.yend <= yline { - // drop edges above horizontal line - false - } else { - // update x-position of the next intercepts of edges for the next row - edge.x += edge.dxdy; - true - } - }) - } - } - EdgeCollection::LineEdges(mut linedges) => { - let last_idx = linedges.len() - 1; - for (idx, edge) in linedges.iter_mut().enumerate() { - // rasterize all pixels except very last - while edge.ix0 != edge.ix1 || edge.iy0 != edge.iy1 { - let ix0 = edge.ix0 as usize; - let iy0 = edge.iy0 as usize; - pxfn(ndarray, iy0, ix0, field_value, background); - - // update the error term and coordinates - let e2 = 2 * edge.err; - if e2 >= edge.dy { - edge.err += edge.dy; - edge.ix0 += edge.sx; - } - if e2 <= edge.dx { - edge.err += edge.dx; - edge.iy0 += edge.sy; - } - } - - // rasterize last pixel if very last and geometry is not closed - if idx == last_idx && !edge.is_closed { - let ix0 = edge.ix0 as usize; - let iy0 = edge.iy0 as usize; - pxfn(ndarray, iy0, ix0, field_value, background); - } - } - } - } -} diff --git a/src/rasterize_geometry.rs b/src/rasterize_geometry.rs new file mode 100644 index 0000000..2ea4b0f --- /dev/null +++ b/src/rasterize_geometry.rs @@ -0,0 +1,153 @@ +/* +Rasterize a single (multi)polygon or (multi)linestring. + */ + +use crate::{ + edge_collection, + pixel_functions::PixelFn, + structs::{ + edge::{EdgeCollection, LineEdge, PolyEdge, less_by_x, less_by_ystart}, + raster::RasterInfo, + }, +}; + +use edge_collection::build_edges; +use geo_types::Geometry; +use num_traits::Num; +use numpy::ndarray::ArrayViewMut2; +use rayon::prelude::*; + +pub fn rasterize( + raster_info: &RasterInfo, + geom: &Geometry, + field_value: &T, + ndarray: &mut ArrayViewMut2, + pxfn: &PixelFn, + background: &T, +) { + // build edge collection + let edges = build_edges(geom, raster_info); + + match edges { + // early return if no edges + EdgeCollection::Empty => (), + EdgeCollection::PolyEdges(polyedges) => { + rasterize_polygon( + raster_info, + polyedges, + field_value, + ndarray, + pxfn, + background, + ); + } + EdgeCollection::LineEdges(linedges) => { + rasterize_line(linedges, field_value, ndarray, pxfn, background); + } + EdgeCollection::Mixed { + polyedges, + linedges, + } => { + rasterize_polygon( + raster_info, + polyedges, + field_value, + ndarray, + pxfn, + background, + ); + rasterize_line(linedges, field_value, ndarray, pxfn, background); + } + } +} + +fn rasterize_polygon( + raster_info: &RasterInfo, + mut polyedges: Vec, + field_value: &T, + ndarray: &mut ArrayViewMut2, + pxfn: &PixelFn, + background: &T, +) { + // sort edges + polyedges.par_sort_by(less_by_ystart); + + // start with first y line + let mut yline = polyedges.first().unwrap().ystart; + + // active edges + let mut active_edges = Vec::new(); + + // rasterize loop + let ncols = raster_info.ncols as f64; + while yline < raster_info.nrows && !(active_edges.is_empty() && polyedges.is_empty()) { + // transfer current edges to active edges + active_edges.extend(polyedges.extract_if(.., |edge| edge.ystart <= yline)); + // sort active edges + active_edges.par_sort_by(less_by_x); + + // even-odd polygon fill + for (edge1, edge2) in active_edges + .iter() + .zip(active_edges.iter().skip(1)) + .step_by(2) + { + // clamp the x-coordinates of the edges + let xstart = edge1.x.clamp(0.0, ncols).ceil() as usize; + let xend = edge2.x.clamp(0.0, ncols).ceil() as usize; + + // fill the pixels between xstart and xend + for xpix in xstart..xend { + pxfn(ndarray, yline, xpix, field_value, background); + } + } + yline += 1; + + active_edges.retain_mut(|edge| { + if edge.yend <= yline { + // drop edges above horizontal line + false + } else { + // update x-position of the next intercepts of edges for the next row + edge.x += edge.dxdy; + true + } + }) + } +} + +fn rasterize_line( + mut linedges: Vec, + field_value: &T, + ndarray: &mut ArrayViewMut2, + pxfn: &PixelFn, + background: &T, +) { + let last_idx = linedges.len() - 1; + for (idx, edge) in linedges.iter_mut().enumerate() { + // rasterize all pixels except very last + while edge.ix0 != edge.ix1 || edge.iy0 != edge.iy1 { + let ix0 = edge.ix0 as usize; + let iy0 = edge.iy0 as usize; + pxfn(ndarray, iy0, ix0, field_value, background); + + // update the error term and coordinates + let e2 = 2 * edge.err; + if e2 >= edge.dy { + edge.err += edge.dy; + edge.ix0 += edge.sx; + } + if e2 <= edge.dx { + edge.err += edge.dx; + edge.iy0 += edge.sy; + } + } + + // rasterize last pixel if very last and geometry is not closed + if idx == last_idx && !edge.is_closed { + let ix0 = edge.ix0 as usize; + let iy0 = edge.iy0 as usize; + pxfn(ndarray, iy0, ix0, field_value, background); + } + } +} From 36aa89f1feb6f6488ec67bf0e2d90520f3121599 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:23:03 -0700 Subject: [PATCH 05/27] impl traits to handle dtype runtime polymorphism --- src/lib.rs | 342 ++++++++++++++++++++--------------------- src/pixel_functions.rs | 55 +++++-- src/structs/raster.rs | 6 +- src/to_xarray.rs | 29 ++-- 4 files changed, 228 insertions(+), 204 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 03ebb7f..5485a54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(extract_if)] - mod allocator; mod structs { pub mod edge; @@ -11,168 +9,68 @@ mod geom { } mod edge_collection; mod pixel_functions; -mod rasterize; +mod prelude; +mod rasterize_geometry; +mod rusterize_impl; mod to_xarray; -use crate::geom::{from_shapely::from_shapely, validate::validate_geometries}; -use crate::pixel_functions::{PixelFn, set_pixel_function}; -use crate::rasterize::rasterize; -use geo_types::Geometry; -use numpy::{ - IntoPyArray, - ndarray::{ - parallel::prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}, - {Array3, Axis}, - }, +use crate::{ + geom::from_shapely::from_shapely, pixel_functions::set_pixel_function, prelude::*, + rusterize_impl::rusterize_impl, }; -use polars::prelude::*; +use geo_types::Geometry; +use num_traits::{Num, NumCast}; +use numpy::Element; +use polars::prelude::DataFrame; use pyo3::{ prelude::*, - types::{PyAny, PyDict, PyList}, + types::{PyAny, PyDict}, }; use pyo3_polars::PyDataFrame; use structs::raster::RasterInfo; use to_xarray::build_xarray; -fn rusterize_rust( +#[allow(clippy::too_many_arguments)] +fn execute_rusterize<'py, T>( + py: Python<'py>, geometry: Vec, - raster_info: &mut RasterInfo, - pixel_fn: PixelFn, - background: f64, + mut raster_info: RasterInfo, + pypixel_fn: &str, + pybackground: Option<&Bound<'py, PyAny>>, df: Option, - field_name: Option<&str>, - by_name: Option<&str>, -) -> (Array3, Vec) { - // validate geometries - let (good_geom, df) = validate_geometries(geometry, df, raster_info); - - // extract `field` and `by` - let casted: DataFrame; - let (field, by) = match df { - None => ( - // case 1: create a dummy `field` - &Float64Chunked::from_vec(PlSmallStr::from("field_f64"), vec![1.0; good_geom.len()]), - None, - ), - Some(df) => { - let mut lf = df.lazy(); - match (field_name, by_name) { - (Some(field_name), Some(by_name)) => { - // case 2: both `field` and `by` specified - lf = lf.with_columns([ - col(field_name).cast(DataType::Float64).alias("field_f64"), - col(by_name).cast(DataType::String).alias("by_str"), - ]); - } - (Some(field_name), None) => { - // case 3: only `field` specified - lf = lf.with_column(col(field_name).cast(DataType::Float64).alias("field_f64")); - } - (None, Some(by_name)) => { - // case 4: only `by` specified - lf = lf.with_columns([ - lit(1.0).alias("field_f64"), // dummy `field` - col(by_name).cast(DataType::String).alias("by_str"), - ]); - } - (None, None) => { - // case 5: neither `field` nor `by` specified - panic!("Either `field` or `by` must be specified."); - } - } - - // collect casted dataframe - casted = lf.collect().unwrap(); - - ( - casted.column("field_f64").unwrap().f64().unwrap(), - casted.column("by_str").ok().and_then(|col| col.str().ok()), - ) - } - }; - - // main - let mut raster: Array3; - let mut band_names: Vec = vec![String::from("band1")]; - match by { - Some(by) => { - // get groups - let groups = by.group_tuples(true, false).expect("No groups found!"); - let n_groups = groups.len(); - let group_idx = groups.into_idx(); - - // multiband raster - raster = raster_info.build_raster(n_groups, background); - - // dynamically set number of threads - let cpus = num_cpus::get(); - let num_threads = n_groups.min(cpus / 2); - - // init thread pool - let pool = rayon::ThreadPoolBuilder::new() - .num_threads(num_threads) - .build() - .unwrap(); - - // parallel iterator along bands, zipped with corresponding groups - pool.install(|| { - raster - .outer_iter_mut() - .into_par_iter() - .zip(group_idx.into_par_iter()) - .map(|(mut band, (group_idx, idxs))| { - // rasterize polygons - for &i in idxs.iter() { - if let (Some(fv), Some(geom)) = - (field.get(i as usize), good_geom.get(i as usize)) - { - // process only non-empty field values - rasterize( - raster_info, - geom, - &fv, - &mut band, - &pixel_fn, - &background, - ); - } - } - // band name - by.get(group_idx as usize).unwrap().to_string() - }) - .collect_into_vec(&mut band_names) - }); - } - None => { - // singleband raster - raster = raster_info.build_raster(1, background); + pyfield: Option<&str>, + pyby: Option<&str>, + pyburn: Option, +) -> PyResult> +where + T: Num + NumCast + Copy + PixelOps + PolarsHandler + FromPyObject<'py> + Element + Default, +{ + let background = pybackground + .and_then(|inner| inner.extract::().ok()) + .unwrap_or_default(); + let burn = pyburn.and_then(|v| T::from(v)).unwrap_or(T::one()); + let pixel_fn = set_pixel_function::(pypixel_fn); - // rasterize polygons - field - .into_iter() - .zip(good_geom) - .for_each(|(field_value, geom)| { - if let Some(fv) = field_value { - // process only non-empty field values - rasterize( - raster_info, - &geom, - &fv, - &mut raster.index_axis_mut(Axis(0), 0), - &pixel_fn, - &background, - ) - } - }); - } - } + // rusterize + let (ret, band_names) = rusterize_impl::( + geometry, + &mut raster_info, + pixel_fn, + background, + df, + pyfield, + pyby, + burn, + ); - (raster, band_names) + // build xarray dictionary + let xarray = build_xarray(py, raster_info, ret, band_names)?; + Ok(xarray) } #[pyfunction] #[pyo3(name = "_rusterize")] -#[pyo3(signature = (pygeometry, pyinfo, pypixel_fn, pydf=None, pyfield=None, pyby=None, pybackground=None))] +#[pyo3(signature = (pygeometry, pyinfo, pypixel_fn, pydf=None, pyfield=None, pyby=None, pyburn=None, pybackground=None, pydtype="float64"))] #[allow(clippy::too_many_arguments)] fn rusterize_py<'py>( py: Python<'py>, @@ -182,45 +80,135 @@ fn rusterize_py<'py>( pydf: Option, pyfield: Option<&str>, pyby: Option<&str>, + pyburn: Option, pybackground: Option<&Bound<'py, PyAny>>, + pydtype: &str, ) -> PyResult> { // extract dataframe - let df = pydf.map(|inner| inner.into()); + let df: Option = pydf.map(|inner| inner.into()); // parse geometries let geometry = from_shapely(py, pygeometry)?; // extract raster information - let mut raster_info = RasterInfo::from(pyinfo); - - // extract function arguments - let pixel_fn = set_pixel_function(pypixel_fn); - let background = pybackground - .and_then(|inner| inner.extract::().ok()) - .unwrap_or(f64::NAN); - - // rusterize - let (ret, band_names) = rusterize_rust( - geometry, - &mut raster_info, - pixel_fn, - background, - df, - pyfield, - pyby, - ); - - // construct coordinates - let (y_coords, x_coords) = raster_info.make_coordinates(py); - - // to python - let pyret = ret.into_pyarray(py); - let pybands = PyList::new(py, band_names)?; - let pydims = PyList::new(py, vec!["bands", "y", "x"])?; - - // build xarray dictionary - let xarray = build_xarray(py, pyret, pydims, x_coords, y_coords, pybands)?; - Ok(xarray) + let raster_info = RasterInfo::from(pyinfo); + + // branch + match pydtype { + "uint8" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "uint16" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "uint32" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "uint64" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "int8" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "int16" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "int32" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "int64" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "float32" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + "float64" => execute_rusterize::( + py, + geometry, + raster_info, + pypixel_fn, + pybackground, + df, + pyfield, + pyby, + pyburn, + ), + _ => unimplemented!( + "`dtype` must be a one of uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64" + ), + } } #[pymodule] diff --git a/src/pixel_functions.rs b/src/pixel_functions.rs index ec7ad4a..63452f6 100644 --- a/src/pixel_functions.rs +++ b/src/pixel_functions.rs @@ -1,13 +1,18 @@ /* On-demand functions for geometry rasterizetion. */ - +use crate::prelude::*; +use num_traits::Num; use numpy::ndarray::ArrayViewMut2; +use std::ops::AddAssign; -pub type PixelFn = fn(&mut ArrayViewMut2, usize, usize, &f64, &f64); +pub type PixelFn = fn(&mut ArrayViewMut2, usize, usize, &T, &T); // sum values or NaN/background -fn sum_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, bg: &f64) { +fn sum_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &T, bg: &T) +where + T: Num + AddAssign + NaNAware + Copy, +{ if array[[y, x]].eq(bg) || array[[y, x]].is_nan() || value.is_nan() { array[[y, x]] = *value; } else { @@ -16,47 +21,68 @@ fn sum_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, b } // set first value only if currently NaN/background -fn first_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, bg: &f64) { +fn first_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &T, bg: &T) +where + T: Num + NaNAware + Copy, +{ if array[[y, x]].eq(bg) || array[[y, x]].is_nan() { array[[y, x]] = *value; } } // always set last value -fn last_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, _bg: &f64) { +fn last_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &T, _bg: &T) +where + T: Num + Copy, +{ array[[y, x]] = *value; } // set value if smaller than current -fn min_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, bg: &f64) { +fn min_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &T, bg: &T) +where + T: Num + NaNAware + PartialOrd + Copy, +{ if array[[y, x]].eq(bg) || array[[y, x]].is_nan() || array[[y, x]].gt(value) { array[[y, x]] = *value; } } // set value if larger than current -fn max_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &f64, bg: &f64) { +fn max_values(array: &mut ArrayViewMut2, y: usize, x: usize, value: &T, bg: &T) +where + T: Num + NaNAware + PartialOrd + Copy, +{ if array[[y, x]].eq(bg) || array[[y, x]].is_nan() || array[[y, x]].lt(value) { array[[y, x]] = *value; } } // count values at index -fn count_values(array: &mut ArrayViewMut2, y: usize, x: usize, _value: &f64, bg: &f64) { +fn count_values(array: &mut ArrayViewMut2, y: usize, x: usize, _value: &T, bg: &T) +where + T: Num + AddAssign + NaNAware + Copy, +{ if array[[y, x]].eq(bg) || array[[y, x]].is_nan() { - array[[y, x]] = 1.0; + array[[y, x]] = T::one(); } else { - array[[y, x]] += 1.0; + array[[y, x]] += T::one(); } } // mark value presence -fn any_values(array: &mut ArrayViewMut2, y: usize, x: usize, _value: &f64, _bg: &f64) { - array[[y, x]] = 1.0; +fn any_values(array: &mut ArrayViewMut2, y: usize, x: usize, _value: &T, _bg: &T) +where + T: Num, +{ + array[[y, x]] = T::one(); } // function call -pub fn set_pixel_function(fstr: &str) -> PixelFn { +pub fn set_pixel_function(fstr: &str) -> PixelFn +where + T: Num + Copy + PixelOps, +{ match fstr { "sum" => sum_values, "first" => first_values, @@ -66,8 +92,7 @@ pub fn set_pixel_function(fstr: &str) -> PixelFn { "count" => count_values, "any" => any_values, _ => panic!( - "'fun' has an invalid value: {}. Select one of sum, first, last, min, max, count, or any", - fstr + "'fun' has an invalid value: {fstr}. One of sum, first, last, min, max, count, or any", ), } } diff --git a/src/structs/raster.rs b/src/structs/raster.rs index ff9145a..6675b1d 100644 --- a/src/structs/raster.rs +++ b/src/structs/raster.rs @@ -3,6 +3,7 @@ Structure to contain information on raster data. */ use geo::Rect; +use num_traits::Num; use numpy::{ IntoPyArray, PyArray1, ndarray::{Array, Array3}, @@ -71,7 +72,10 @@ impl RasterInfo { self.ymax = rect.max().y; } - pub fn build_raster(&self, bands: usize, background: f64) -> Array3 { + pub fn build_raster(&self, bands: usize, background: T) -> Array3 + where + T: Num + Copy, + { Array3::from_elem((bands, self.nrows, self.ncols), background) } diff --git a/src/to_xarray.rs b/src/to_xarray.rs index bd32335..b5a747e 100644 --- a/src/to_xarray.rs +++ b/src/to_xarray.rs @@ -1,22 +1,29 @@ /* Build dictionary for xarray construction. -In some cases Python will build a xarray without copying the Rust array. */ - -use numpy::{PyArray1, PyArray3}; +use crate::structs::raster::RasterInfo; +use ndarray::Array3; +use num_traits::Num; +use numpy::{Element, IntoPyArray}; use pyo3::{ prelude::*, types::{PyDict, PyList}, }; -pub fn build_xarray<'py>( - py: Python<'py>, - data: Bound<'py, PyArray3>, - dims: Bound<'py, PyList>, - x: Bound<'py, PyArray1>, - y: Bound<'py, PyArray1>, - bands: Bound<'py, PyList>, -) -> PyResult> { +pub fn build_xarray( + py: Python, + raster_info: RasterInfo, + ret: Array3, + band_names: Vec, +) -> PyResult> +where + T: Num + Element, +{ + let data = ret.into_pyarray(py); + let (y, x) = raster_info.make_coordinates(py); + let bands = PyList::new(py, band_names)?; + let dims = PyList::new(py, vec!["bands", "y", "x"])?; + // dimensions let dim_x = PyDict::new(py); dim_x.set_item("dims", "x")?; From c15572082f42bbb9e0ef100797d71b104981dfab Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:23:38 -0700 Subject: [PATCH 06/27] handle GeometryCollection and return invalid geometry counts --- src/geom/validate.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/geom/validate.rs b/src/geom/validate.rs index 80f78ac..68afbf6 100644 --- a/src/geom/validate.rs +++ b/src/geom/validate.rs @@ -43,7 +43,7 @@ pub fn validate_geometries( ) -> (Vec, Option) { // check if any bad geometry let mut good_geom: Vec = Vec::with_capacity(geometry.len()); - let mut has_invalid = false; + let mut has_invalid = 0u32; for geom in &geometry { let valid = matches!( geom, @@ -51,21 +51,24 @@ pub fn validate_geometries( | &Geometry::MultiPolygon(_) | &Geometry::LineString(_) | &Geometry::MultiLineString(_) + | &Geometry::GeometryCollection(_) ); if !valid { - has_invalid = true; + has_invalid += 1; } good_geom.push(valid); } - if has_invalid { + if has_invalid > 0 { // issue warning if bad geometries Python::with_gil(|py| { let warnings = Python::import(py, "warnings").unwrap(); warnings .call_method1( "warn", - ("Detected unsupported geometries, will be dropped.",), + (format!( + "Detected {has_invalid} unsupported geometries, will be dropped." + ),), ) .unwrap(); }); From 13eb7c2d516ba142990ec13f6e3689031c165aed Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:23:52 -0700 Subject: [PATCH 07/27] update to new Rust nightly --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 25282ae..bcc0268 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2025-01-05" +channel = "nightly-2025-07-31" From 3c389c397a167db492a9b3c114e69d806f4b20ab Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:24:06 -0700 Subject: [PATCH 08/27] housekeeping --- .gitignore | 3 --- img/plot.png | Bin 1981828 -> 24553 bytes 2 files changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8c542d8..511e850 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,3 @@ tests/ # README quarto stuff README_files - -# Other stuff -_ROADMAP diff --git a/img/plot.png b/img/plot.png index f6ec8763490ee70bcab457582989d0962aa0cde5..e7017fbee3b9908f84f953a3c6a2cb4f60095458 100644 GIT binary patch literal 24553 zcmeFZcRber7e9QZw6s*Hgh-N+WXnosQmBl}Ci6leWK$_4WM*$IdylNj-g`^gvO@N{ z&+DR3pU?OH-S=PjpZDYO?dxM)@9}z_*E!GgJkN97WNwQeK6vIJ27@^)DIp?~J11@Syzt{x}H%pUZgLtCtm(UTC^uoJi&NGo|99trbWZrJ4r`VS12#+eYl2z`{)*acr-8y;6gKaLC8LyEqB zp!@%S4a@NV{jvyo)C+E{(=#%9IIa&;xg5DL@ix@NJv}}B$wX4lL5iE_ZKtvnjEXlF zU5>>1q-j@_lPVj@j$P^Qg|Ecg6<(@?9fb?+%ChqEq+ETE@78N$!}TKVIW7C`mpV0` z9gay%WE!at?-^l*y$pc8^bhKJSj=BEA3jssuKbWfM@J{EL(P_iH!kZcu04@^Ykgk( z#}^luBRb*C>P38F-jozn_P=FStcL?;6Vx{tRu?8ZbY~m~J-N!WdrZi~c`e+Na=!68 zEM`Pzb=mI5WR}|?gVI;N`|h?UkNHSx@~7!DR1V9U(O;Et8M<}uM3-~Vm3vW;tA4UH z{@t69Qgmi`xNLr_rEs>Kd^V?egV`ka$BX5Lt@Q>@`t3{g2b8c>~HH&ZLCcNAAe#i0V zcv)H5rk}LkYcs`E#A!EaF82#;{BGeM4Kp8zdUnM9+Dri(4UHH7VrS)ptENq&WM!rm z6l8t*7Pyum3?@X5Z>xhAL%87WxPHE9rS75F{rmTgGMmIqt1rDPZH|{Br(m;R{oM(R z9;4R~A*-Zx`q|#Y?jP>HOHOY`d!)YcQ(@3?bJS65tTDO?etznThS2)ln_`vW&Npf{ zmlnJ9BFB33%#E_UbWLZ=$i(UVl!kK!H^%R<8n(Xgd@|@6Yd%`vRP4CLy*d_aZbi^u z62@b$wbWH~>;3JMi_$&HSwZ_d5Z9;prX^uh&I{Pv4n zr%f*2T(I7Ty%EUqCy#d+#CF+u^*TGv5ja$6~!r?lD_# zu8dY3b@=tn)pe}D*wH^AfFr~sd+t!8x42%&`IUI#E6Dh?Gt?lh%BBd>N1S$5NeI$h5adjev>b%%1 z*R75It-2zI$<8$NWUj8K_-!7CW?R&U@vsT3|N6>3TY7?gii*obg-I!$cJtRu!A!}s z7)(Me0g;pZ$_Q8SX=S4{(OVIGJP|fOKP->maXdrixNaI7*8cCSiE>ff=;pN-dZ0ZD|SU^^fmPjE%aem*N~8q zPTQHn!Hfn05BPVD6AoqW=IR96Q_psrt&JsZ_~%W@Z&M0(Kmn)1Gv}KP<8=N+a+^bP znFm>luPg3mg%?F^e!fjsh5h<$#cV)|-34Tc<*$hFB-?!`{+LBBf!ZsZROSj9ZgkTmek-#SFem)jHq_rTfBRb8AdoUv!AuC5+P zuet1T;|LG!H@o?|vCcGY5fMIEKGU)2m*r8{pJ~kvRn7i*NmVW@PC*iCoZV%pt)_r8 z+jLy1Q7^w3X?J|E+|wgo>P>l@f;z?8WX7F>Utfrt5)@KgU4vM)#R^tNLQPv`#qlN* z|3=Ln_EbR4W<37M&kygAhdRPD^m=(hed|myyw*T9W6CQ}u5a8lG&aA-njXA*ShS+s zYu>=m3g04oVp2>!2W}<*6swjzDYAm{()joyjEI|3^Y4pH#37cCXelpzy)qhcm$x3$*qt!mZBVS72m zx9Y(_tha#S{abBqZC$o;ml_1O>_kK+VUau+kDk6tTU}E#xH1~)J!3a|Ey;!j<5X}H z6GiApCw-HKX2ly!Mr`d4-)t!@jjte_pDz2Y^-)0u>y7uavA(-@?`7}qNKr3_UtWxO zLi1^6W2yfR10$m!m^c5Ynbx~^q-kiZSI6IxQz)pahKqH9`qRv9_zfJ} zj^%mJ(`e|qp`~xO8EbowQid6&SJ72{ zaUsfBqjXsQ0gvpkvC2U{59hPvV?(=dd)U^ChUPr;(V-c~EnCYicm;T1vu=ZTI!hcD zR2y)>l!haMuj<^4UHd){vE$7vcL%kHN!Mje?c<*sb;Ss~PiQFviyjNij+3W`u237bDL?q%-by+}cPR^tvV{!v66U zHd9}g3B|crr-+a8|CV9GX1XF600V46$t zpWHv5pU?vTgf1ld@(4xDjh9D=(_%t&A?*58JUVThF)bq@Zdw+==07%IQY+wwjmvR$eFDDqOdnU$!b-5>!oot@qaRU0bJBJ658E~StZo@JWh>l}oI{^hn52a@1hx}H5ewoB6#Y`V4jZE4r0GVgjc zID)~VFgs+9Ai*8MjmMr^yD^ny5Sg($fMiU5KdE8Pc?F?)vNqZk@3uW9{piXjyD|nN zx7PJy2NiB>7#FRKBov6}nDh+{dJEPIZ1(hTl74-0Sa~?tdG|g}8$NLH2nq~7CA#d& zm{M?cu^X4dz5M;z;IFSo)u*n^Tz|H=4E7Gorz`z?yyYTtwqk2Vzg$>EO5J&64!Z?~ zJ}lz7T)aN_1VWnD%G_|5_S(`+adik|WWtLF@?Y~-(H)5s)M8yL4W7Q`0T&nQK1-y& zEWEy$Q(Pyo3GrbSD(l?JZOQs_IBdH0u`gZyb z|H8dqu$9@y8%N9FOjEp3cd+JLXuaD6?pas;lYUc-{QY2XlOjvgeI4A{Z`W5lioH^vpK9GCNke1o`6wa(iAZbC>(6mAk*P{4Paf1x!?&0L4^qV1XX@T)r24CZxb z636r06lJe3!i_V3c24(w;!@TmAx^8J3l*`qw=dY*Tx-hEYna^BsR=B5N#zja*QJ5M zAerRKP?0Jl#oBUKg=DTVk<4t{@jt$(XXwyd$bI5CF~xtt`el>*qF ztg1mPu>MZtoW22Z!ByE_n~5Y-bcEptq+2VITV0d3VP@5rOy}#kQaA&N&x@66d-G>^ z4}OCUP)vUy8OxAZyt$AY4{riSqM>OYQyIR2 zt9QK13V-Za7ZK_$xXDdvJ>--7{VCBv!F)Y#)`Ib(tg;!p1{`Y&u5VAYV>gJ%F4rD$ zYRF>F>Agxr^YPCMpT?wnl~y6?5sy;;qfD3cpg5>36xJ}?di!?6tA0c zehy%PBQ=vZ8u0|)KHWSu0zBmta2tR&cwR9CM4$@1}qs%9KWoR`zVXma;#A^ zfMX_i&P7-py1~DBi!Geg7|GM@rU8-Q9-^nvLOh| zf(#4{132|Htlf-lzXyP^io;yJ#~`r2f&lq2>DzoQTf-}a@6iD$z4R{ZIJr^kbrrD6 zCJ6A$5J7zTry+89fgj~8n)Q5G8mMe?I?`@VviH%~!vKEU##n}>t11p9Qf)0)%tAg^ zMh4z0cVn?fd%8EzbYr1I?`5dtLV~&{z#T^X6|V22I5|I3=V8$D7P&gD*?}_LLXqFa zJ6_L@@X{Ckj%UFidz7@TPwEMJOhK9!z!CXmzzzO)kw#1OxK5St1$?3*QVOp9G$a6e zi=B{gw#i2(Rju?JCL|3=sjtd~J^FG$RzZPGLDgbA-x&$k6DxH$jOSnKWxM=oGF`FA z&e|w{{Ov3_---h3d3o#p7tg=*jE0@)-(1Xzh1_&DE5+V8;PeAx#`+Z(GPP6U6c+a; zAyCeKA!0%Hb{tX%{jx6AYs16Cv+7$5>ZaeF_mxA8B13S{#eW@!1S*u`FRnpMSu>!byOQ;;yk!eLX4sU&=;B2Q#t*VU^QweYNma&nJAOI z7=H5Z+gnuvf{(tJ439OD~*)&LVcG2qAhn)t1 zYo-EdH4R*P*=z+B6&xZrZ7&`=+g?}V^8>+p5pF0RkTNMA1H8xzC?t0{P#N227{CZw zqz8&Qw*^*)Ag?8lQAkmnosZn&L+R!uE3A5rrDnhFt6O!E_Xb8SILU9IRQu4wI$lK50U(LLZ!-rN9xs6$H7l~KB z4LVe+`)1y9BE-lIZ?4UFr#LJm%tF4;V+m=a1s1@>H}}nrwF>`B zcL!dv)mO?s$6);3=Y;nG!_fk^G6*qzk@sQ>2BV>BK)nYO@_U7}NiGwD>@tLJEZ^Jw zd|qCw>0UA7T^Rq%08pIhYC|{}7r_^kdIt;G+q|NNds11oRq$WUI&+)_ZCBZecY#n;rdL${OvG{b|ti!rD&}?;Yew% z{vJ=Ox%^2)M8ubt)u1^JS%VfpzO)KT+ftNl!h2nQGz1)z@yxTM0UOMR zUT06U>D9ji6p`@A`T7CY(!x?Zfbr;qB9IskI`5-&g(#25HDm2b%8Nm8A0~&@mdNr< zgO&m0mk)Dxodjh|if*kH zZ;_*u<=1dg@;EYil%TA!je3wWyQ>x2lu2{-okS;HVU05Q79EO`o#54CA)R|SyBmYS z(MkF)178z!CsIIGP0fzu5Zv7pg88t{6s@HE$~7tAXYR?%bFPKJ4@Dyd>``!;{YtfY zg~fDNf^P`}D4W5vq)w>Foaz$;YqmvFf3Dmjz6?28x;MF5%=~DBH-vURdmvwjFYuuCATdqv3;PWa_U_WPq%@-LS68?_vcZnM!K)Cb&;9bc}?X7UfKn z&e!y@bWM}NhEn&1_MsVOM@W_ifZo#Xwdhdg@g!6%9Kbp0hNIk%RlhMxY%RObvgZll z)$=qlRx{s-iPJPbKNYJ$Y4}=iHQlYCV_*{1Ch&3%H}J;A0oF1GSm5mA#_KLe2yc2F zOgx@}>>TL6ZvB4st<{b(NP$H}enUQo9IrRuoP_0Gd1GT^K49idzZ|&8o;ULI#aRNG zvz7vz?sQ9ng2r~_;spL7KA+TEC(YS(rH#HyNF=P%o><_C_@)y1pnYr^PpgUp!{pX1Mm_P^4Lu%<&?7-C`0Yl$k&es~vg1`e7jN;MJ9 zh5#(e9*zhsfB&-gC^(n6TeLoIy}GIGj|&AiXT4{^?^OV{4sYRhYKIfg3c(rI3--^d z`Q>>oFdwmwTbp=zMJ#^Shkpd2gbPxErHEDpnD>gseq3ty`?I5B?{0@5JjDZ~`wu*3 zqe>*`R#O@vm+)!>z6+o%WTTbGtej5bl66vfum_wi+T;>6%rlQAf705Vx4tnsQpZex)xYq3Q7Yja#()~#Md)MOMaJ!GAuGqo)g7}cIc9|(O-`DSG)_h;BSqfo zRhMF;K)mM2ee>EcPZz1D=9AA1gtFqaoX{4sF7oyzBs8wO zvvU}E zna2o@E6T)#;1pW1`;BAyGY@f!Gg6-v`;d8%)2{w>1|2E)aZI-_zCiOqo@!V&a_|86 z0*~t9N-j3Eg%}AA{L;o@eP60m-&#LNeUjt=lZ5lgh%Cv|F2&bWQ`RFSGNaDtPr0N%<@{VO;3yCg$ywy&BV^jbu%CEA(z^8`&nC$i+IKa( ztnt+$p^rK-OjmhK&Q(7@M0)ZBGsI<7K-%6`*b*TEqJ!__^W@4N3^!=fk;Z?iO=!n) zE9h!|{uylD#(Q|wpghry&})xVHlTB~4#Rq`pH3^v+^R+E0Sf{K%G05Oi)$Bl4-foo z@h;$)d?Vd{oNiglu@Ot%`~Pd_umv?D)~%ztn=~hMHLkPnY}X;veYB}jn2=E7wr|0n zqG_!22P;X~QZO2Q66gTDHVmfsenl!wZ}1`)!Wewdkm~fb1uKEoKW%jF@aXD_!64Du z14DuGpR+q2)$7%UD1Miye773M^wTdr?EhrX)v*E<3uFuM2(q&OtnE|I*51U6A=z^) zS5J7Tu+4w`Rr@8TGuWEY_`ILLI9=6j$}u{I)Tp4HXTt8^^>;m^^fOzZekgq1-gq@g z4SPWHTGCS)#bDR>cdE^b=w$f*_Z-9J;Qo1PM=M$S@kb3EicIG~T`nK;x^V4`w40&9 z3MJiF=RYp9t7L53)m*f|CGS<*iellQL9om@Pi9RhRu}-4Uk3g%q%Y_3cr$|Fs1kx$ z0DOh!^HV|>B2-tTw?3|T;U}O7PgKFN1hNhYQ6P$PA#Va`JMCIQ<;aH+1NYGD8wXEb zfXx=6tDZ~WRd$0QVGlD9RbJc!u6V$#oMY0pfglGWv6F!VL6sJ*%GcB=l?~#+$s=-u z@YN}}sm@{M=Xu|p1~m7|A2eYv#lw~rf-=WLC$0$SE2(nXF z+r>#l352Mm{E5>z9B!VBFEKhmPdnJH%^}#TO@LjcV*Akg6PPDsqMk_bya_^(AM&R> z;7R5f8roW0MMOnKA5V2>HElxp7nPQl&cNMB+ zz_tFB+xYbZstr{{2JSen-i;7oO}%;`@%U8$E8N5WcLyM4bOXX@*r@No3A$1soYH&N zSLV&L;c7zBs2=ct1cGAcm5@JC6owkmiJ9Up2fJAh_1fu#loW5oIy-F6M^@Z8%73yZ zklDk9h>Y{&7i_A=b%5!gx-1wO8BviaGMX$Ypr?brU?kbC{mVeCPO5#&O$h=0)6WM2 z@LtPuY&9ftX^;vEQ;SpN5PKV@CxD*6J-7?|mLTEzBtT1irU%8Xj2_4skBgr*69$EHHtrS~5{x*?K8C zZzLplBm{Txgv>R~#rw0Yh6EkkbghYX91B0pgfX5B2on38+$X+|=OE@#bH2MeJ8o z5hAY;FA-M0hrIzG9rZsOybC8ql%a}Q)rDD9&q7IIhq|NvD{`*n#^~!+GR}mJ1W7I~ zu{y6^9sBn++#y&0E+N6j{H$?vIKw5C162}p*QP(A$M@X(XE2Ad*Zk;tfF zaaB_3fp2epV)cTJkOMwA|LYlKDHnK>i!0HuU=vPC*cvmpUX@VR&uu{AvcxC#Z6*-` z26qGD3a5K)yNO<-6*<>-RhBaNY|i>Hf;oS0o_ZuCRCCXS*6o{@ zmaF?QLw?&hA&_?1N7C%tu8UQ(@Wd-nKHm;;=;gCk={;5}A@Yno!DBBCW_B*W{oQg> zOPa|e+7O!(YZDU!`5$+%m=}$_zu{H85+Y-CRdV`evYv0)<$XiJq{46+Jm!Kg9|Ehs z9$QjL**EkC9_d=9nXM*H`C!%|xydurKepZJ)XTu@-SB4CJAprI2hQPf1Z92%9!Ihr zfQ$VDiXg}$czA3F9%>v;YPa7o#C=+F!lw&%BJ#fC;>RNQ*^{B$DyNTd9{(CV1 zwgy+463XLEdfUKSg!cpJWZQnK#yGo%JnRX^hSF8O`80`y7uJepd_*;gjF_m_9bf_W zbEEf<>8Bn~dKM=gg5BF8Vm{@9CDN=jIQ_gAx2#qd#F19w(|88{`oCx$kVMp9#Qged z8SnhZtlDxKW|X%zX$#-VhX7Y->4tj7|W1Xk8n@uS`H>CRC#_k=QHODTJe5@lBBz zHhvnf(@HH)%Dbms=Bw4?ZSN2W@LfV9AQiIVcM&#*lYkD=;A-L`B7jIx-KQg2m7Jmn zYV$x<7?_0v#s5Abp++D%DXC7m78%z#>+Xc-p5$DY)$C@=a)Cc?HwXtNK^ziN%ZZ+x zhh8*$Fc?<}-}C|_q1o3Z46+x)AItSx_1m*Tkpl56>1(Ju_y<|=Ishze`eZdj*Aa3> zQy6ku`cHL{f`XCJfUYmlozSuFavF$sNi79Q8lodnB0YzuY(DKn9>f(=z!!yw&6BUWC#HgS5TeW(O<8QPBzr6U5n`60pnNH4Te$ z{nWq_Sk@rW@4zFd*-o|TZvjc54Z;&*VnkkwSzBA5wHb>>lzFU}r#rBq*`DXoX;r)q zx2BbBN#cs^fRCflKM;aTHhAW$(boQ1>zz*Ol@LSbR{6^A&Xr|GsM$b*q0DzX;ha!U zRYjx4jo#>7xHPAS z5yiJ}D9?GuKJa9sQXM;XQ_VKmUT5UK4F?Aq;};_)tlqGaqORKYwf0{Wk=x1eC4(U6 zEFjB~TEqOz?nWEPJy39@Vb^3#9s(m9?LW-Ai{d#N#cHgdpEN-rG<6CwqEg{6jJ4D*#CTLggd-4A&*`2Xf z0GUNO+$2W5*kKkpU``~<>D{}RT@vpZeE|`*(L1-DK^hwtyB|ad?~Gnz(9qd=zgzyp zI?Bo(WGr65vz_Pcdf<1~;bznYybiMQDXI}rI?{}zs<@>X)dizXx8GCH^5qtv{jRaFEzNjzAt*Tk-dMP6CG{jT3d-q zau?PSG$(_pUypbk6Ev|E(+2p*!RY#kZUA0QVUh#!4yCT zNhSZ`TmnWvl$!aL5X>b9VfSSnYWBwm4bsTAtOMCEG3 z(os+TSkn&8aFD;0f}ZcMTfbS3KSll)Mb$iJvB2;x$kS@>0u&}cF- zpfI{-+{U}SvZS=KYIr(MF{u~(KK_vld)C2ZFL_~%)B4Ve0|W6d5&{sD$qS9^Xgk`m z%5_DC?!gL~3J_%t8^7skleBEF&}(sPedibkppjohy?fo2We*?;0dN*;ZJ3E zVYUfHFPwh)zC7d57Z}10H7rrjjf8VDHe%Bb5FKjq@prpi0RW4+>g%Dzu!dHGx#~lR z@cIL}#smF_Hx+i@-(G)gJoBg;2hY6Wg~R5KA)!Qj;mW6>hU9s zN^yw~29rsJx3strb?QCaOi<2{I-dA?v4?bO#~@S;6hXrbo1xc=2t60IYmv_a8P+w253vAit41lW|3#qE@d)pT`)`>=MKfyGnZVm_Hi+sKmD#{lznAKv)@A*ny#Uzh}M6{jqhtyQxSBGmM{Bu0PR zw|2Xz)D{7m=3ettSCwPb)uDkiuiuC%sufu#QobH2J1=KvDcG3_WTHD%ms5;u)M>xvi_|E4$*miVxDHw#A6SA}Pn zCjK2QIDvUr1RP}&5%AF&fTAJUta#LM`MLG|puVMIiJlgRpoE)v8-tTT8!xel`ed1{IxDAoPVoUza?E zm@4Xbn|C~pdpyqXAcffZ^EKjRyw77Y-)5w=i`3Fhx2~?D zy3Ht2E)+5$p`L9w)(4Kcimj9a9uuGXPTdnw-luTUGs&goV|#X%OpS*2w&lwWM}KlS zO-vkngeSekM`#ndpGIo`h&ZFNzzGiLnExTk11{{a?|*}c+`|VeW5O?G?j7V=Gqk8J ze>%{3W%~gSH_v`QdhqlSOx(1>ewM=D%|U<)@xmJrqzp6BMp@&}Zd3rGW$>Dl3JNQx z$MiCCw5bOl)CRGF2KUZM-b3&bSOZW$;59r*mjShQ#>d<;a+$|SD*=CNftc9?NXIkPk?tiNI?U}@Z*BcN*4`Y2L|fUzL1+T2u$DR8TLn_gBYZD*%)_j6b?EF35*&n z*LkGAq2PV&ibOwnJ`SlHk=_^l0H6vuE16P%@YEn4PX)8--A4MW*)kRf)=w!#MFJ5Z z9$5yFHYLDiBo5@za@d6A`yaL2Taw^nVFy9H*v77@|aRq7H z#nzA#uv0(QQ@-oy&`Wr3)X#wA#H4HQK3Ps7D2n?E9t9&u?@gj8WC~f$>lufoPcBEO z1GY11(OCYRBiUlxS@HcmL&(rautev(#ceegTqfWo-usUy6SBP*h}`=~{u9W+%#QDC zp-KEFHB4oWrj)_;p1=gMk}46oJIRbD0c5XJ!))qan)uAJc9 z78Fwc`#?~3iW-vCkd@hXPWPI5J8f2qUS^Uux8KatNzIZsVK(vz`~rZjVa@hr#>?qd z9TAaNyhD}1i{xPJm{4)yHI1%%lFXru zCS*^V0;EaW?e|R*`A#HUSFSB5h&n6@jt-a**wKH8qw2XUrTn8eemEESmc*QMj}}d2 z4!Aj?@j+#2q?;&t95Q+JT!-W@yD%CQNI-(vIy@)>nhjrsi3?SS z-GeNLuvJZ0J)qhs!9s^qeoe3bzNx;6fILo5d)HJCR7oLPW2ATJ9;m);SG86pxe*SE zkv8B^jy{Fmr<#x*o0a;+;IxtY87xs`B&PrkjhNaKS?aHnh5uP0j6cCP8o}?+KYd#< zJ7B4l0>T8R{|f+D$|OfK(we^lpUe)6O1I?Q+h+tam+b8-U4F1L;f03(;KmQSdr8kZ zi-yuKOy}77&1q3XVrqIGr0s5;v z`5C2;Y+>?TYaj155V>^pzDkq}_S6|6k+y~>FG%ihHA>Wq@K|V6Erx_a_T^cn?-Cd% zIU1-ZsDURy5?0``%jA`fNmz=OwGawIBB$l5I}XQ*iLv|XG9G^O3Zzhu-Z#|@o8%Ycf`qUMyx z6YWWDILE@(O2w2!?c0OQq}V-|IH!`bWD?F9blg+c6fw;q6;3mpUX#d&y1o!RQX#;& z*^I~yCS`jPVzjgyYfBFwtKbDH904Ujes};6G&u;lJ)bBS#lNk+O z0F_8{fQpN6!hHjo38*D^Yk^SC6ntB9%i5iPRk=D0_+e|E8tyu`9YG8otL0=TsrPAM z2o@uuC9MxN!)Sw++#6azikeKJVB_W`FVbd&Efnp3QLU7O6`xQl?+e?E=Vg+im0bP}>+PB>O50`)aK!#E}H(UeSLz4Lf6!&)T zJ7Q$Hd4F4Jl$^e0<>7Gpr6s@aVZp<%>B_E<*Ay_ew)_38K>+?ogqozJ{>d+9wti09t&>=LJ(rtX&zyBR5*@YU*W=&C&+Hx_ z1_u9Mt`E=G;7cAS{pE%Ai?KZnona%`qD)d@lce6;@$ttb&NDxE!rxi{Gph6ff_w$K zD^wf&@%3fJ?xTD%W2sSbJCQ~*B3r&2c*}uiDHeRsV&vxUcc{!f04+&21;3l6cvha2 z{KSU>CsMm9oH{7;k%atKI4=k48|yUrG|Ep%^R-nm z*KyNiXOyk!+8g&>1erQYbws!^#6JC}6~VK3E-J{&i-Cb=q@d#LwdvgRflNxrzrH-$ zU@Rk)f!{}&bW~tPy-3E;h$T(A1o~N#HbWCdsRY(8$6H!bg-S^};bmdNm}gyO0*;DWT!RfIUFn*PVX$Vb6X1ZghAMqrjvT%T)WnD1D?HamG_U_SSp#B{tKpD3umxFV8ol5Ir5)5;eY;WM^DTA1wa7Dedaxzi{k%Y@LGbqH`>*0BlD<-;agFrJ$M27s;mcSFp-KnAS=lq+?xko4|`kygRcE4-R z76PeUI~cS4?u@jkJ+#|zE_&m?6dR+Inc(i4Tg>#A!O4(g4;Z9m9?E`*CF+niAN9`S z`u+I;G4X8eEU;4#eO9coU0r!=J#yraGP7aK^CkxDPGt2%3)5IXfh zj5IN#OEDhFhJg6DYZTsp;m-d)iM-K{#k#)f?qS3(?qRNH_ocBJ|Ry@sWYE?;uLNVqX^Z$)B zgLOnYJYa_&=wFCS(mzd`6oO-6dW+18R*CBdRoF3N;)b(V@=AQ-JV0K7%7b8GLX;~c zUyk30BEqwMM^W5GO<%F2(9K(13Jtfzs7?3xR1X&t;D6ZzRL?PDAKH~Jrx8pPo1m!%PI-B*}(EH@%dK3G##X95fuv-RF z+pJIm1vhl9fF3!qY$`xO%`I|cQV-P{N}nG%VYa$?g6@_p^keDdtjv#&KDLJ?iOMvf zHrjVvaO3x>YnDbH6^8)6Q9>sZ>I^|bWg8PDOhLj1)YpR4(;$J3YMMXn4fU?Rq3(cy zfP_S)Lz&0Z=`s(=xi`0BJ5`v5ZkuM$#y3DVK+aE@_bE`gk`TXkmQjFTkqWzwa;YV9 zll^kh^1$RERMEJQP#ZK#wui_$nP_N0$3h96>bFs*%?rvI#FEy!qINi*t%Kk#X0HA@ zgMjmpq6tJMM~zOvc^I)J2_Fa$#AgZ;o9=*@R52}%#_EDQjyg9)IdbfuoEBSzcPVe zEZ755eu393!%<${UR0PG&k-Gz)w=k$@L0%e+MNXTuTz3&mP=v+9-N6#QRd$N?f4~@ zG9@lnB{I?{j_4{SO&KxiH^@hL>(I%!U_;LD6Z%5?g`BOZ=YfVna1*bi$ou zo;Kr$h6v@y8|`fe%8iC522Mhr`(8z05)}7g518MmIfuzC3{XBOc*mCdgWnFrgUB$e zKkEPV|EW}ua??lg;Ms@&!X9QyL6rHy?14d0KH*~bJMZ6yIx0%OB1&G$`^no8`FrpF zeVwJ8d-fL1k{8P$;UI%XY!NhDfyZ;7{060+8TbpRa4>kY-&wWyr)&77b4zH;yD3GyeFA-K*eG z^g+8>qDEsDtIBxG78gLh(d!o{{o$LRff4fqm%!;fNa@}Sto3FeIdJaX_9@A-1KLRD z?9P#)MYKpx4TzU_#N+aTL1N()vE^`gBcFJt?JOmIt%xokiz&vaZpQnh~b27`n~CjEXem4+YU~cHJqG}Y#`U_w_0PHzq)(Ehm3`>dY&L& zVh0$&QH3QR5B%d%fU|gZU#j4uxJW6KJ*{mU0+jc5WW~#bYOKk*$_E7W$KLDF(Z?$H zAC*p;i5QwcXn!a0W+={l$_dwk?TiSh-bfV#Iq}dRq6q(9uT4O@3niLuJXh^2(jDB( zFcLYgs*L01Iv<&l8j_J83}yE$ma;27?@npaTDkwQA-nMk=(<`mvpa!acIp$`692U$ zeEk1fJ+;CD`UWC@nm>LJPmaYw|1X??(3f~Y*m1>F()O&{PtmF=%*qGIv3tqCecB30 z$i5>};GS0Zz(SB({ZORyTM{YT_Uc~0DTOpOsig;N!&rhuo}C;EG*GCti45i+Y9{aadts56X=Opa6w$sJrk+MXsb0&XbcI zo%4Mf)U=Bv#CY^q%UbbZ>F61D`n3j78$3dDGiECUH(3Epkszpv+WqQzC-4)1Vq;=r zjv;wYd_AIqLCZY=bq)_ii$E_SUXlyg>lELUN4J8nUO)^xR5t-60U3V;P5qdhKo|yf zQ=;TK4d@y&hV`(Uwr*A;JvsZb#qN%NI90F1`iQclzIs$L6gQoFv7`RywB)Gi6we!6 z1%I$${RN?(dB*1#2I{!_fw7x!5EQJ3zSCT2R*L~eI2u`0CQfA!HDjP;Jbe|2ee6~4 z-ffxeM(`4a7kK!};9I_thw0B8R}}pZiFM&>@GTLMF!OpI{CJZ;BoYizvg%DUX%j%b zv>^ALY|nYvuVTjz-onYXQCRW!e}N$y$l$8aH~$fGaF|wc3@MVJp$YmNQBSj2iYmyd z7xPAL1zn*o_oj+Ocz2u5^4g|5-BqCI69X79f!_qw>}YNfl0(~#OR0ly4k||#hz{LY zU4S7nh_mo^_#OKMM#aPez=pIN3R;lX8~QI}mGFUdWbvZ0dDW|@>#WM!Q`~n}G;jBG zn@7xj4a?3Nji^1k)u(1#Pl7Vm09Wwm+?&gT+Rbs2P4J4S6ANu45KA!ZiI^CsBsm@e zJ1JW0n<)p(O$sZFAI6hwIw%(&1#Y1`k9JPRIRFqGh))=(p`X?bKjs2`;9aD{8JO7M z0h_~~Z!tkij4wSw6^P|F488z4)f1!C&wCN`fEuNU&O-MSMy47(?;nEz2Dm`M7*q@I2|F2u^Z+anA&L1}Bw<}Ut0B|&Z_Z0h=-ua{I3 z7P-~m^D~y&rt>?LTY@sKf)yRs&z?Jn74IeSWz`*}LW{6O{qQG|ypz=hQlhqbfm6fg zjRMK>i;0Wz{NMb_mQOF|EI#7z`KqUQcPhpubxn4~fO=iuMmB0q>naCsHNi z`vBk3@h4WhRf2XWzV;B4+2s!Y#dz*_QTIr`W^t;&&pbKEA6$N_DqJh~TiLPmd3O(W zd^)97o3nUjH?BWfyxc~IC&D>^$wQoUGyQJ>iBt9!d47^nDsTKK`vGEvEAc7FV?94`SwP4rU&Vl!`_v5h<;qmIf+`&;&w_|08in({0pT* zGZs9qiL}jR9leHhX4xzDC0bkz^E4TJ`NA*RoqMq*n!mQBTW{mxsLtN@*sX#j`~&V3 zS`btDhwo#4KBOXbq55V)RhC8JyI#K`xy#)uedx>d#Uc$QWu*TUcSH;(bJ9jwf0t1G+0^(=cNf>s*K_O6a!QrWO> zV@$~2cihk;1w0<-9{ajzIf$zGczIcb2Lv2(QA30yYx8@FqtJsGje1+Hh>XYq}*q2 zs_Kq6i+T6?oeZ({%{0B^OUKynO*~ztqR;)ZjE{dLLy^*F@Ry*+Fmc*XetGaokkjVb zvuT7QJhrhhG&DxOEJz0i{_4jNOn_8FKZ1H)!=n~Knc z7f>no4@^K^8PpD(ZwWlYCZJQK7HZb0lLO}7LCxOyBaE#=6epCSS@L%UPleWTw_l!Ybe4Dc&M*y0Yy;`6|CV7DYGb!juA*9@FOAcAA)Aw_3!%x zT>(oZHnAp4_kc`+S$-t~jWzJ}f+=2`$I-;NSdb5(n2-yjt`43wHh@kQ9QR$(f3?=C_b3 z9TpT_%gUc%gKE>HG%IUXX$rEo8ils)|3_}E#I>gw$v`0FQ-Ni;gBJWUh{@lb;xN326csKB9uOHn2pjDQli1>%nzGfAWC(^z;ai62B8y>H24MV) z!L3kEX1q)UbwEK^QW`YlO|qiyDulNXHb6iL#(hM%Lh&d$NkI5hcK*Z(VpNr6U%iaR zavA(CnAk0|=eS0abff_Phva0Fxja{iX&BC-t8NREKIlMMuNrILzuKPT3oZD=SH3j2 z^=9S4EBL_-r#N8YK0W-|`au$om>ke~v~IYTKs0Oz2`lOZ96_!^2Yg?RS}2Sk%c$pN zJk9+`gIq)*ALdVlAY)m}1y#v7c!I0I-w}x2w}DnF|4CR3X@MLVM->N?F{(gI;?uX} zxV7X6L&{(jkV))*sg~+3`-TTsB|)3)cj@li2TRNz-R;s@I1b-kMHWVSO}#zR&XGRsp^1UC|&izRm{nK(C;IT_%1YX4h_dBUM^#y zk@TH3hZ}l9WiO>^7RkBc0OeQ&UC@ES7HMCPToXa1(=~*0p@cIFbt4^bQ{(P>m~D3k zO;ekMF+Z$2)&62rVdyewVhW6s+5P|?<-ta~?9b^_&Nfnli52gftfF8_666+w{mukP z>w|_SuxgYzp|dc6PO_^j8m0-gO>)(DNbb$qZ)e`|!Ltq~*Z`|=M%4rS*(s0!E@Gis z;2JtQKoCnU2BAg*H2(vpK$y%2Nj1SpBT_WL1_;D*l*gdaoQpgi`=YoL6~-`8h53NW zv?J{o5t}QBxM4K%P#a`cv*7#%X=u>RF37@UK1dtTtHX80M(e|gh%G;L^jbfBk{l1` zrFRWYL{Nuuh_S#2p)>{6d3%KE}j7+4Wn(fe%!+6d9i3%cUn zDFt&konsiJ5sVBq$-lJs$k|g!%cSz?GvO>`WIU)Z0nJ%}iHG-fH-HRgT${PtE%tTA z~BuZem50HY7Qt%rCS8YH)1O4!Ydr?QjzHt!ArgK-`2EB!Oul)s%zXiG>z|s#akX86`SloIl2puK`k&2<3@(@Mox5 zLL5Y{2v-1E-))$HVka8ihGHjbNk&>eyf25IQv9HsWK(b(6=;8fDK&aKM>2)}84I-mC)NfdcMkWdv>3=p1lQ zWwzj3k~w)SV4xNn9C8eWU;HSW0S^}ZY){aD=s3gB5)|K%rV@=GL0%ILi9!=rkyKCk zrMw&(^19sJQACOoSX9(Now7)T2)>i)|GC%Z$s|y_E=LOclwf8Ml1+3~pe_+)GEiaB zhIudED1M__NhgeNLvuD*Vek>uyzX!wD1f$?@?HDR9z)Ct3Xy><>XfKA2@Qkv60l!A zId(UTidi)hnw;@(_ma{^A0_OdzwksWstv=%(7YMZ=im?64{orz%De*G^Ajm$=m9V7 zN2@g3QUg|h8z%MnqBOP&q?!KcP9IWI9%j~~zd@5X!SJeJIGitpx15ca272;V0A3}b zqb6wxMgrY}cD67hn5U<1c!d(FF{G=}D{a?kL zPbhKJ>5hv%c)AuCX>|!%KM~I67-TDonAr;BLIl6R>uW{7r zi_0)|8ZZ?_uJy5fb=botI(=p)jzEHMacAku(FyMx_^m-b=AhULI67*2GEMU=t#=>fy~&h8O*wr zmL`sHi>tbR^45i*wfBxk;WKyh7yNNI!Eid#iN#jc;o}mR0d!wsHe<7l*nob>hN*0c z(8AS7L+lH=0-#}511eNZTFuRLMW~!sd0#nnx-FlAfg~6bo%S-TwsWAXhoQfd(Qu;= zeXeyfFl)Irwt%JtMuw8~Dy=gOW_#Aioe?fRP-&xZv8GVph>=DC?Zj-+iEV=|XOWat2jzl_v%=rFmX2-8Jq zKqnxZ!kT)(y8B{jh~0W?>VUER~iuij_enV#myriG94^xSI+1pLI(4*;eD9t8?zMhw@Ppxp4i|aw znvK6arGxdk?wrF%>{qw=&jXvR5B87en7LjVB;5I_I{1Q0*~0R-YNKu0qEZAc9eKmY**z7m)<`buksfpzNwfPw%5 z2q1s}0^tbIsSL-NyC8r70tg_000IagfB*uC5uhWPn5Lv62q1s}0tg_0Kv)9j3|jRs zVgbU^<~|5SU4Twy)LV%1A%Fk^2q1s}0tg_000I#cpd%Tv2BA0zAb;My5`8+;N9prgz?XCo*Ny zg6zBRzA|v&z(|#8^ytyjuV24Nl_`wEXq~n0l_q_r^m*53EbR2c3opo?d+sS4Z@jTx zhL>M{IqV{EKLij!0D&3_(2=YWJhw#v0R#~6E^yC1_s9t+oFLn8zrEdI(GGAN({s-~ zC;AT^AAkID9E+b<=Cy0r%27uhCI9~SzvY^1u908;>Q~aYZ{NIgg8XpXZMVrczVQv| z)TxvGTwH(s^+Dc_yb(YE0R##JHYoVaM+9Om@au1W@MPp;0b(so;Ss34z)d&ZB=hIb zm)&;TP5$*SYblVJChb&SbkRljDC8!aY?4=t#fuk9yLR#Ux8AqjdQ1NP_rJ@nx85rM z{ontcSC^na+;Yn;a>f~F$Pq^zArmJ~lxLrPRvH@{<+IN|lk?9%Kj_<&KLQ9KfPf`H zr;^720R#|0Ao2n_^7Zwvf8FkAMJ+7X6kdIOy{W9K%IAwOzL1U`J9_TSE?>UfzE5F? zxT>31aM9}{mt1m*oPPT0vgxLqR*)m7B7gt_ z2vj2wpMUzSrgmI|00OBN(D{-_AAPiR?b=l~-+Xg9`|Ptt|5(mV)22<6{r1~Ww%l?{ z(Ldw|3>Y9f`c;^8F57p%`(63tAO9%d{`R-+(7J85*+zc(%U>3rDzClvnw{r{8*V7Q zd-s-0FTK?MBe(Fx7hil)MvNFCTWqm~=($^Oy|tWr>ZygNh4NWP`*hx$Q;wT&zB%u{ zYu2oh3opD-h71`Z-MV$N=Eu!%ZQb9A1=v{j*=HY_J$rWEHO@S~pZ@fx@{3>mLbly@ zTiaMi6?L9nVS3}`Jex>uhVwd`HuS1`)JbVEqCsu zhLWCp?zyt_&O1xz&YkUNdgREF^2#f(*ysEC&zw1P?0esR_uca2AOG0yuy3@{MzV6{ zN;}&>|M|~y&_M^;?XJhQ&VTyTpR%{qa_TWn8uqI72rCR(pE-5?;SYb9Eswq#J9ezB zTD3|ZdE}A2<9t8>0R#{TMu5@DU|h)+0R$o-pdCv6<9y1LDKc-~Jh|?=>*S-4KFaF| zY6o(sopzG{`mg_z4K~<79(w2@`wvtd>Cit+bJLq|z9~l@d8CXPGe%B6`DD5M_S@}0 z{I#Q{d1zX?bg2vo&HSH8X|NQeZc<|udEM@xi>GnB4_`whCPR5m2UMYtkez?5- z_S>T6()7d=PuO9K+L6~b($2vtr<`JU#Pc!>;6o+;`VR+MUwt0*A0=8^TI_!`Sg>G$ z^ytyUF5S$TGwr%*`Lz?N|FEF#u0QqJ(~g$b|F3`jtNi9Szp>{U>OUrE(r2JpQY-Cf zYe!8xp39ak6Akaw4&3Xnzb?Ic^(uB*MaOhBPe&xR!@7F)YWw|DPdz2tVb$llsj12C zz&ro=qT!_ak1E=(I$C`B<(J#xrTPyAjzzHo=~rAdoNt>yO`z_5V6ER_8FUX4VLXecT$+$sV`n zS7qNjZ>-Ky#u{02KH5L6(J1@cXP$W`d)(@bWULX*%sJSc8}%4VtOXdXZN+0U})S{;gv)iKOi zBeof9RMI|fb&NB5JU3Y#(2Ui2$XK1t>|w2+=4XutXRMAx_V|MjK4>4eI_lYDdXKMt z?Q0opL7MC_t8;0er`~SK>a=IH+Mdmb(}C^LiSj(=g8_X z+UMmyGgn=8m3_>44zd|oZ?w+ZwobA-t(n%=R_CMrQ?Ju!?&6Ct&K`HlX?6Cp$I892 zM)mA+=yRT1cB{jeu@*YY=0oV~;KRU7QbgnEtyroLGQ3*MqVnfIxBu7@f5KSB!)J0?8C8 z99|u}KILGw7QKLrL_jZms^UWT9_+i;)mt9JZ#yHnFKhHbwJWr!9 zUgwYLsH~1QIY~#W^s|moYACGM$ND4Qe(5Q5aZdg-N?oJ+Gmk2&U;y!mU6 zn~u0?`rv~PM7NXB5!_s3tJ7kK9c#E^kf{>?!wx&l9*J|xqoZ@X@4mbJ99@0&)%M}s z=SW8|bp%=6G^vkMfBS&P|K!f4v^tlzxrV#yGgr*qW43vxS)JItk#lX6H{N)|4t>oH z(bbFg-+zC5^;T{7N*4>#5nCO>^^)}Wp;XejllqJnOB!OV-;)qP009Jo7oZ~; zk80&~i0-`ePO(NYWznKVc8A+JUqdhpQ=yg9sdcQ>NmF4P=`oWXCC#r`s>Huo8P!L_ zft{owoZ6N;|4>6{bCW(t)gHjv;^m0`3BztohcNPAq`?S$tDOpryI7 ziJoN<7!DzTKr#h%5?gN4eGYWkWyc+N6dh{P)n0VyLx&l)x2J#E=O!JdE0(M%mSTrX z9@Dbu(v&*6PA6^Zkf=^p)SjCj)1fH0q<`6a8HUliY_{2EZrN0H{MWz!wLMu>d%wDx zjILs&9y;++9Sf65tHQiZeD>pC?EX4gSN8x>8=Xw5?WfO=u4YsC92L91*fE`)_>-Uf z#C|SxqNBE@{>z9JTU~XYh4(4!g*j!l;&a!V6zg1D>e+P8Dg3Fu;txOku<&rv&stBn zAxOQzij}r|+1i8G=g>*o+tguI6BA3FJo$+L0tg`BEw1fwhI z=pV}ctkromX$MYMANl?7e=oU1YSsiU`}=DCV@-myV+9MZ)y)KpB_00L*JYPoX0KkO zF|j(#RcZr2U8Yut3(F-PhMQw8-B~WZ|Ni^-Qkc2Jl)7S!CdW!Uy}Deft`wBpA=lyE zYLYr>a)!im^Ki4uJ;&60FaE{Ks6LL34!b`2?Ks4id=vF z_4Zp`x>A!ff|fhFs3U*6lx4L^|H!@e+H374Pj#Z5CLNWz>#n=pLOD=E}5w**UBlg?j@n|!nqxC`(zLQ(Ge!CuXc`e zCsFDoJ6*ce>2!L?s;W~e@z*6*-+S-9>?BHE&Q|NLqlB7rZE_2v&p}~wI_}yrESEIj z+-FnIw^qQh%d*{e+m*Y#s_(VEwPUIKyl4z|;Yg%D%lceYnoLfh)IEN51XGu0&h1p{ zXWi38H>D&YfB*sr1TR3RGI(R|fB*tD63|I!y8nlU8fu3}LpAk}V`rrg-KRst=?*>g zPA~F~8t~3&dK6 z)b`cq&*@<1p0DQCGOqUCDY`MJ?n|QoAfcngK_v~N)%{@fA6v8?bl(%Lk9MSW)KKfH zqxzL5(-SB)>{nOX(mhmkuNvLIM1Q|^l;2cnccpdYd;}0cAaw%CU9FayA%H+y1)P0C ztdXsZwLxU2OP4MgYvqv4S!bP^Yjp%OR;MTX*?D7iSTg#`O;(4|KBjqEe}reOKYBA(sA688){>kVYxBm8HJWGd z`=EPrSRIFq6{?q;mHqzOU(bEti3M2iOM?fr9=ZpImSO17p|*+E`{zIZdEQ;DP{NEg zmoKB=>7Ete{N^_^*3z{09v6DdN$RJLx;I8)(&O4rx#|A<@6UYW8{e?=v^uC6tK+&} zo_fC1zM4njD>qN8b8dfMZr=zmZIN3S-BZPyTWgooTB=bM@KKXT<8j+21?QoH|;o;pi!L(mg|TKN{Uv#hT-o*=w)8GS)wi z*w0z+^XHVanmt3@_WQ6laLibnW!ihcXnSkftoflCE40@sUdHNV+U0l4BYR>%Z>)dx z$XF{lWvqV~%2*?+8Ed|C-aaP*-Ii<-KmY**z7nX@pVt2i25La00Ic4S3pBCHGI@> zCo;W)X|MS)Pfj?)i3Lcz1@%V&0R+4YFn7|s6=x!V00Ia^TtG(>)0}hxC7pv>Ds}7D zjSU)00r4FI2q1ufCjmw$Juz_-0tg_000Pk$(EToTrQA|4U1W)b00IcqTHuf)AO9t> z0O7WRE~s|G2`AVK$?0Ch8p)&ED;Y^G&5cbAQ5vBn69f=I0D+_l=;o4fTVp_%F4nzP zN+n&km4pBS2q2Jr0o@bJTE)%Y$oSDmAGP;+(v6X=l?shiJ{BwSXm|Sj^UoIr`0TUK zMAvNB4dHg(bypif|A*dMGS6OPUpMhmFx_}+`|YXl zgD?v{&r$8po=+@5=j2)&_OxhiL-HU}1c zC%yI7TlUV$y32BI8a8ZLrm?Xx_mJ!RB}X++B2Ykv&Suc{`u#bo?EsL>~D(Bojb3Ww>AB5JtiyCGc$DP(7fa0 z#*NGDvBw^H$Ey7h`9*l%e*0~E9;EiFfApgt*}ZD3vnU-qb`y(1XiwGDSi6% zv7-lTaC=|3)KST$%Uk8|^ZQBHE@nqoSANnaeTTdwh5eSokx8w9F7-WNz<|8ESOa!) z|NZyp9jo?3iGb{#d&cj3Z?zJ_O~Oqn7VU3AfUX4;9QBe@<62M|C2 z0R#{*E^x@pFCL$b1u&jF86to{5(NrFJL6Qx&Ryz6UnWzVtjo_!A4V4`UA%a4-tGVT z*S|{NzJ2qKRr|qgce&b9l(^2VSN-n0@7h=O?Ag=)xop|8yj=9N^IhH-)`!WHCyUOn zbY9xKCn10U0tg_000IagfB*tX5tuMxg3OyY&mNq6^UXJ9`t<2yO&yf6W5*h)V&q3B z%k`wU-g;}h!}#QrPl`3K(nir?ab4l{%rnm{id1ZpYS9xphyVfzAP{MR0h6yOTE5q( zOelJsg9spifGL6D4O3mSnmuch>v!RQvH4YBKbu&9@ZIz8*qUjR)^#)M^1tx$N%QoY zcH3bt`4GkN@Xy_4xgL?v(xg#k~Vr-nCc% zf7lDb)Ot11$gji`?MIx_A%Fk^2q1s}0tg_000QX~hkd zI%-TU5kLR|1Q0+Vtpcsq2KV|hnM~*Br)-Q)`qATB1Q0*~0R#|0009ILKp=$zj83Mo z(Wx>52p|v_flB8`7S_wTKHc*sZ(VTR4~PXQd^Y%ufER&k=1O|Wmvho55QUXpgH&g9 zGDuzWMgRc>5I_I{1Q0*~0R$2#!02QG8{fB*srAb2h$XF|AJC>=)vvh2noL zv99rNLu!Bk0tg_000IagfB*srAYf3SW+RfTf4#v;!~z&>NwPu!0R#|0009ILKmdVA z3b0Q|B%6goA%Fk^2q1s}0tg_000IcqN}y(=leMZa_eCIm0zcfdXCGn#(%0_ss&SZe zC7pYvd+(5VJs*@70n-A`d`8pxk{<$Y0&Uh8`ZAeJ=jW$v>=WWgk82S?009ILKmY** z5I_Kd6bdjpnZib=$_OBU00O}X^uB)KLSg}eu_ado5I`W#0=h?t>3u>>=jX?lPNg3` zu3b;yfObc%cg#63v+Z8zEk6-JAcX=>sA(;$?v#OW*IKp$_eLOC0m4~>^(A)%9059# zjs!muKmY**5I_I{1Y#oa>}GGjN-RK38jE6D0KFOwLWXJk@Kb7K=d%g8hb1wjA- z1Q0*~0R#|00D+_kuo6#FT9SGofB*srAbCnPE@K{*gW009ILKmY**5I_KdmQTUL@}tMgOP*GkH~Wc{=zRH z$o;4}GjZMuHCqPFsaZQ?*3LU-Vh#oPH z1xQOXQBMRAKmY**5I_I{Ymbn2_RAzitI5oK=EdAepMYGB00IagfB*srAbN40$ABq^NdQOQyG<(h*>$@<}ucy z8g6A_PGv1xgL@`Pz?n~&q&iU-1Q4jLfHQ}%wx!_i2q0iWfR3aIMe;xZ0R#|0009Ji z3mpC471t08;G2;Q5Qv~a(pPqsmgdGLCQwH3$)iXJAbrcCH)gH}dxqrO@CkuR1c+&^M2mAn7UnV z?>BqP}@(JIvDcMf|c^jLss5l|Wg5I_I{1Q0*~0R*ZR zNcu`VbSkTUG}3utsriU;5-F?)b1CE0dX&|VKgZR1xsu)l z9jDo|CV9Jnvk-`|03FE)Hx5NZ0D-g#wA=5)KM)I$wpORU2q1s}0?8K$W)U9dPA31! zp@s+`fB*srAbJq61z85SdNZ*yNOkh|Bj8N- z^OFbHB7gt_D!?R3o&y9BC?!BgvXoBf-<{BH^gF}?gwDSjBdmC>YlL<944y zFZL6OTMI_Nyw1eE=$ACPC3M<=$&<-#4q^c!+~{o?Yk61e zWiqv8qO-g!ol27}OI`>dfB*srAbGY9vK7>KmY**5I_I{1Q0*~ zfy4`>c0}^Qe-7zFEI{I)??_d!Fa$QYaD>C=YVh7ozzMH(`<~;q6L8vw(AV0v0e3Df zKxk`eAHGE(Xn{8C2B!D*&^)7(7@dqtOHd935I_I{1Q0*~0R#|0ASME-9i5Cx`%p{- z5+LwW!*7=m3y^?@qzVWifB*srAW)@1o3&4fzDy?5`S~fE*_!z&3D+Wk00IagfB*sr zAb&gz0+p1XHAM<;VE%hfmUmK5Pg|Urt|YvwwC6`CbQM^Rh;q{Pn|kd>gwub z{P^+NlfU@l3mH3htZcsd=F+)yXF1}CBV^gKW!dj}g8%{uAb5#V{6~%1=N2R7Q;&CAZ#st8BXIrdhicD^|#D zx7{XJUwyUgx8Hs;WXKSiHf@?b_uO+bZ{ED@dAvaY0R#|0009ILKmY**!W3{~T}}TC zvks;16^jX!8pe9aA%{pqLxWs;>808F%%4AB)~;P^A9oJ!xZ{qp)mB@{+_`gQk3IHq z4(I(`y?V9i%SkI&u5{k=6M>ow6i%#ULS@aHB(MT7Su!x%P&0Pgk&P!qWl!zRoJn@6 z1)$;xAP^aW5IT~-?|=E;W?}*EzWZ)@>7|!MLtYEh(xpqKW5#n<`Q>RWv&ZzFtXT z65<+`H1|Qkyg*yVT7buTnM|hh^Ha7+cPjVae}7r9U_oB}PC4Zi(fN_*pMSpe>C;C# zbm$;YKKZ1*HHeGT7t?b@}ohbVQ) z))6B{$c;BzTLO^~KmY**5I_I{1Q0*~0R#dOh+Ri=&YU@U5V}Io4L96izw*A|Lq!5^ zlk_Uegp)%RaBH(-6Dvb47&prbIFsnI2Y7=30tmPWFgda)%+C(n@7&bI0yxjY>{*kF zqHr*t0&SUg)&xqkLyD&J^CKI(PNkp9axDS~AbTU5UqZ zetsOxc9H#*gliE%009ILKmY**5I_I{1i}~SHTLTnAF%+8PKMuP+!2951qg)=l(%H& zgt(UENzIqebHIKfscG`6D(ZwaR&_BKA%H+E1d<-+%IIV)9zzO=00IagfB*srAb!NDH)P+KIkQCe!)(DI1+i zKYCn?K+FY-#lmWph= zD1^Cc$FXP6o`E!k*#O=^ib$piAbo!j=id9-Z>5eWni3NxvMh(j3_N=63 zPNg%~tp@bm5&;BaBH+w%i%D@QCISf5K%gyS&7HJfCX$c|`%H8yeFAbh0tg_000IagfB*srAP}a& zk|j%|YuB#XTh`atXWu(-Mvffmyp=(N28nho`}XZCzy0lRWz3i{+3%`-Lr1b&Ij%zh z0R$2!aQY>8K0qu$;+mBTA%Fk^2-IAlRje&Ste45;#eus#1l_6QUJ64WFti)69f=I009ILKp?RK5C8i0SBV8kZ1Ykv1d0nd6DUpp zEPl83kDhz(x$^YWPs=;+yd%#&_ndtH``?%L?c2+7#~ru+$yFR-bg~LGEi(W}7QJe${pQ|YE zC*sUsi<2*9O_qT33^Io@dhN;_$>_C7T1(_Si?e4fLJX5uz`X$#i~x%4T-l z)lW&d76AkhKmY**5I_I{1Q0+V76Ob&#-f?RD&(Ep-Ss%J0AXo!9|RCU009ILKmdVs z2sn%Hn5?bl8I?q*GAb=WIS@#YKw;RcQ?*)zzd9j>P8s-#KvV>rXOnQ(+O>!iN?5xR zaAyP%Kp?&XbR^^3c9a|e1Y#_(#lU&Ji3Nyp15$JZ5I_I{1S$~7WUQ54t(VE9c}6C+ zG&eRys5p^vp&$q#fB*srAbKR|p`000Iag5MzO(&zbuZVgX{@ zfD|2pMNNP}sprttX|D!ee?R9@F{#r?i#7qa#_l8CM{HKmY=T zA+TP{*j)TM+J!KoJ2tl0{I$IP`xH zk6S`4Kp4{80|5jOKmY**5I_Kd+6uH;3-IX6WHOzfpRzG`(vKe3B7gt_2q1s}0tg_0 z00Jo#V01EtjZT#jNSDC28`m!+79d@1PHhoD009ILh?YRBwW_PWOeWL$`6^pWb7K=d zOJ9W1zqoKxTJ%dy2@zN);P&jkcmAFbA&^J`CQ?@3tj^@J%CF=K1Q0*~0lxxtB>f6< zJpu?IfB*sr-1PXUdBg(n93X%I0xkk9@9F}>VFVCB009ILKmY**5I_Kd;sW+cJceUk zqqGtaqm#wkouddK5D|g5rw=YD$!7(h*L|#iWuv0UIfwuP$q^u|H92ibO?(La<$%`) z5)0r1kV}IQpi>!yDtRJ+00IagfB*srAb>!s1v1tiA*M$YHP5Id_6dnfOHd935I_I{ z1Q0*~0R#|0ASMDS7?FHo*V8)^3lNj%kzyi%00IagfB*srAdonLHf!FbzDy?5`S~eZ z3Obd3c;b7l+x80atq~N#Jp-b zUY|1IKgH?rV*MWz8VlgW^Zlo?yh8v11d<_;^a+%VPA20yr4|SvfB*srAbAV#f_kfQ3wlcqPHG@YLxYC4sE^tcuQ z1Q0*~0R#|0009ILC?b&bFjqQ~MH)S>hu9k=t}U1RatA#>vJ%0w3d66EFI0XR&5Qve0(T?N}7r%QQu>dh@eTs?z0tg_000IagfB*u4 z3nYDXl0|p|e}uRJ0tg_000IagfB*srAdoBpqob3_qKQv!2ycx~TbV2AlGpEhJ|3|E zQ3`Rb#NRC(mQdJA6gd}xbpnLA@+2T&UcmXinmuch`OL}DxIofJC#9viv56r{R$m8&i6f<2=6twVXrA zTuHyJ$MpyxfIuJ&z&}H zntbCM->`e5d+f1C-t~My009ILKmY**5I_I{1Q75i5c!T|VfD1*sA;p!HnV?fhf;?v z8yXty&-$Mocid67+G;DAJ9nb@75_?XYk7OlK%kZa&hO~#S(9p64DMYb6Q1TYo1X_vFcQ|wYACk@#AH$ zz4nrU0|(lMOP4N{jvYJ7CYx-MXR4DY^}Aw8Ct2#tNh?>bbl&n40R#|0009ILKmY** z5I~@eK;$P-=9XV4QZ8J$&|YyT_jsl6b#tY6QbJ!Q?BHS+Pt zA7_v0n`O(EiGEisO`bg2o^z?;v-(=HWJ$5pIEDZM2q1s}0tg_000Ib9CD0~y62z;j z!US+p(7bb<5B2r+qOZ6n_6Lbh%gt=NH?J^BX&dHB<~6JD56r3bO-Kd-3pjJH0=^B| zBY*$`=@K|cn$r>s5cy7}hPmphtq(r@H3MWr8NwUVx)&JOO z_Yn(F<3>o&?VaEE*|R34r_wR1Y0@W9(y5Hm!$?sPKmY**5I_I{1Q0+V9s=5t)KFKG zDM~Bx5aw#KWyuQx1Q0*~0R#|0009KT7C3b2R^K8PAZ+!V`!PD1gTY$_+yuOax;p1Y zDLk3*R<~A-*KtB&t*Vpymi55Lp4;c(Ts$=98v3v-E>Ur_zre*CtjVPNA-ZxF+^7^i{FK z@YniRHu9Ap7l$uUE|k?vi3oS~qQp4}#9v@|!&JKxvu90;e`OL>1KlIU_&y;K+9-rD z*91MRR0RP95I_I{1Q0*~fj|X*-g43#!~z6L9LLNVos46vQDy`XKmY**5I_I{1Q3X{ z0E_TMx_KxZ0tg_000IagfB*srAb>#F0*puoE`HP%gU|LK3&1uZfj888H!wA4(#a=? zHe{~kddhaWBY*$`2q1t!7y|4Q z5{5GOK%m9~PHbw8i;;JGVp{V!_z;kQTMVq;jyTw0(1VO=4W`88iU0x#Ab`O70*p?s zZ^97-5I_I{1Q2i&_{#xvwjdV3O^4$MAb>zofuwIfNvE=CvvUvu1Q0*~0R#|0009IL zs9s=QN3yQ^42oQ-c}69%2v1a6f^r~$00IagfB*srAn+f7ySBP>Ik5o$@!(yA1sI)- zVB-X_w?qW18HplAY0e~bB_r`jP>37>w|SM!smww0@HR?wCD|{;LxXV&0x=dC-Y_*= z`q{H4W#99rHUdfCCxp?-+B{d>6#)bgKmY**5I_I{1Q3X=K%zz@hkx+GwOor6^IA@b?-4)%0R+kk(2*=B#P~8gnJX7UzCp2^7wybnctQtFLd9s{+_Stgnwb%MC7W;(wCL{v{ z5I_I{1Q0*~fmjO+Jne5+5DO6NCZzD*1zvdJ1-a#xTV&9nLEbMab0(e2GC+Kd00Iag zfB*srAbDm0s?8K4Gq|G~so-ae?6tQ|&Ki&ze;H1dgUg zVC>kja>yZvNJB$|TzcuH#_C;VNAkc!Z#bJ+0AsB{MhGB)00IagfB*srAdpyrAh!rv zxpHNmTYY`K=&LZ@efQn+(n~LihPfI~EzONh#JU=1B|`)dKmY**5I_I{1Q0+VIDtNW z`bf8K-LkJKQ>LtUvn5NG*sJc`b=O_esZ*!*zV~~e${|X>x8-^S5bz-ItLu084Y2?o zpg08q1Q0*~0R#|;w16(f>SH<9qILT!KkYDf?bM}D;4AX*a(CI%*_BgQsscd>GjQ|1&gdyN8?P~H~ny1&)rArqX zIB;Oz=^Jmnv24Ei=Ib5LJIUh*I+Y%wI1K>=5I_I{1Q0*~0R#~6ETEmf3OatC@->~G zr<)c(iH>A(mr#zfKS(glVT8AO32P0ecI1jc@&pRQUUO?0R0wQZ!&!4}i@hh@wU`RW z3=4cP^>@D|7Qiqn*&$Gkz??aAs=2J_HFPSAvTzUq1Q0*~0R#|0009ILs9qq*eL|`) zmANa~BE*~}IU#@m0tg_000IagfB*u~5SZ5bwHYDB0x&umjV7lQ2q1s}0tg_000Iag zkTih^&2=;>yQy>HoKmdW13M722poB5kLR|1Q0*~0R#|00D)=+7@e$E zj_VLW009ILKmdU#3;cD}et$F?3lQbXQZ@vlE>LF`P+umK>HMlM8=cDP?YI&F1Q0*~ z0R#|0009ILC@zrjFjqQ~#haa@F%U30XEMmSk};s9hzP`1;EjFnC|UL&FWC1|Pi%ZP)v*9{D!n`=oPz)Y2q1s}0tg_000Ib9B9QRWNoi?r zY+@yzN}dSLMF0T=5I_I{1Q0*~0R#{bh9!9j!WOt{>%pI=Iu;;oaPEfy0tg_000Iag zP-B6NwQ7#OOeWL$`6wHmN*{QYUdpDEm5LQV-^9d8CQKH$c7 zS4^xd%ECbe5J<4V@P?^&?PkxKl;B!YwJ-(fNQSA+y%0bEfiMN0uG{i^!~%pV&b<&o z009ILC@PS#CQ$0jWHOzfpRzG`(vKe3B7gt_2q1s}0tg_000Jo#V01EtjULx3`w4H2 zVpwbaD;wEOv3i%!;@TvX8-c(Csts%PS|Gw-^QG7yB;TWoPw(Hf)2GA&Q~}2&2&6_J z;mf-wW_0p~8*Y$({rX9#PMu`;-FKJgpMO3z&nOi|009ILKmY**5I_KdhzU50@RPLTlv2FT4f-z*z#w2|C?`|YNj z$Oi!g5I_I{1Q0*~0R#~6EKp~Kxd!p_T!5fYY-w(6iv0x2HEY(${Q2``^5n@`uXgR) zNkc<}%$++odo1_O>eZ`7Urt)Na;5V&ke}=i;*Ztq#K|a5lJr+|u0|l)0zprbtVXZc z#7g!QsYa4(5I_I{1d=Wg`(ep{{_~&G+S)2Tdi2O^EB(B5>C(Jo&W9;erbxGL-LkJf zefl_GCF|!QdwuyFu>i?xQ)+_%0tg_000Iag5I+HDB_7j1BO(#|PGvQYI+D3!#ft1} z$&w}2WWzNGAb`dALhEn7F$UB_U&cavSoP2s!!WlV#Ukca?8{``dEuwb#n3RjceJUd=F(3jzor zfB*srL_*+zQMW%uEI=fhfz^+5I_I{1Q0*~0R#}JtpKBwwe{!j2p~|Y zz`wRVU>{-uDplir1Q0*~0R#}Jp+KEAfl^;4lj;2Yl#RKQe)PB&0R#|0009ILKmY** z5J;f_qmuzNI{SlEhFZ&clI2RuJj!xHe2+jh1e|%4&b~W`9rtWB3Ks8D3|X_~PlyGG zcN8YEeuC z5I_I{1Q0*~0R&^;Eh;}l+{`IdH z%QKb72y?Bx*`pllTCO%hgu*(ZdaHN&tlaJS9svZt5^#QR3PB$DjkRo3k(BmOShfZ! z@=$OH0R#{TNTANj*Yu(}nrCEEOLJqB=OM}m9(X|Bd+$9t^UO2lo_p?*e*OB%$dM!E zzWeTzwQJW#rcx9H0R#|0009ILKmY**5I`VU0nZ&tEk^h5-DTp$iSo)TugIVO{Aby2 zyY1w}6Hk<0y?V)n2@~XvH{J-gw3_?Se%BpLEI{zi+yMau5I_I{1Q0*~0R+qlWUK+j zAYNul=R22*I+f1dKKS4RnK^T&{c7L7y&Qb-!Lne%0vRx1fLwFUHO^`LL;wK<5I_I{ z1Q0*~0R#|8pn&H|lN!qU```awrcIkBPdxF23>q{@#*ZH_#~**ZbnV*JrkOEghMaop zsrE=^0j>7o$o^zaGNMuX+fA+S*aN3 zBY;5M1!m8h6!$8`xBLm;C&Y87@@rrFnzXgG$#KUWC(l3sybK;Zc)dm)K76=rvdJdv z9p?Z72q1s}0tg_000IagfIxBtJa;5DeD$cKj*?EDI=R+fJCT3;+uvNj=L-Z7KmY** z5GW$>$9La*hgg6jFdRYv0R$2v!06<grbK+~aQHurUmU z&8-&i5eP)UR9LIm!nuXN>h($3TZH{Vygmt>hX4Wym=n-FLX7VdqMg%-q^cI-iAdQg z1_B5mfB*srAbgLtdPM00IagfB*srAbyx0tp|YWOTBEXMs}@KmY** z5I_I{1Q0*~0R%)!jZWf&00LnNeDb3qeTW4JOPl*3fB*srAbRX$Ga z4^kSA(ALsEUcTMt_?Jtb_V)C_CBNrO1fnJom$24yZiQj5&iPRce`S9Vr*-&=K>7q` z&zh9JN>k%d1?WhI%FWFXKmY**YAi5t#nFR^1*mZ&aC-z0Kp=GjnL2BUSL=R<@aUmZB5I_I{1Q0*~0R#|0AXI^U+8)@s_OSpAQHI*++zbH(5I_I{ z1Q0*~0R&PZknjnVbR;W$irF8eI2bXm#a)UW4f+`(2DX?w$AT0{Yiz4m)4~|oF!mE+ ze-N*&!+8iKOMtyWlGUD3uMHi^sJ9U1GcEA>kET6Q`&att5I_I{1Q0+VK!Jr@o^&m-009D%H3A5f z79ixcv=84RfB*srAbAB0z9S{;W3?G#g)pvpryI7iQZ(cNQw7xh-EBM zeB)oGCB`)^?d}!_OAM^>U!7QGNNj7k&E*yYtEKQ72h08-;kFrfLjZwv35?BLd@iv7 z#oL^?*WxZ5MF0T=5I_I{1Q0*~0R#|m5uhXK0>fbh5I_I{1Q0*~0R#|00DE%+nY}aAbFu+N#XMZ18KsUkzf0oE2fMA%h2eJ16}0^(hnVI}Mkq0pF4A<+K4iolYzO z4*~)RAdn|Or!tR&4+tQD00IagfB*srAb@}YfrKx@BQ4F1P3#k5;6Wh^1Q0*~0R#|0 z009ILFeUKc|Gw}fVgXEPhLI0Llwl}y4+IcE009ILKmY**5J;5(EAgbNxv6ZSKollU zGBL8SRigWuiIvf58zxFdr!B%PiQ8TQ!yA}b8D=|hF9Z-kAPoYHNG79|KNxi9b;JTB zqb;cg0tg_000IagfI!j&5FxsyqIV5tuR2q1s}0tg_000Iag5U>EFlK}&hJpu?M zOrYHjvtA_@AYqM4br3)R0R#{TR)D#a!TLrv_q5N~3uDe?+KnUo@CQ&Zw>g!1tGA~Q z4&aXI%aXa0>1%VXv9sx9x!N=;f z8*v5#2q1s}0tg_000IagP?><~4y2Qx$5Qm}-CL$kohtL^&zBcpd{Oq>Z$J6o_r52u zz4n^N9Lk*5(%jg@1j;f~lfj9}A*!$>3!~(>r#i}dop@$xtcj0B1T_!qgId9%P*=eVp^1iF)1D(oh)VKx# z1Q0*~0R#|0009ILC?OE!N<1r9uFSi0eSN*?%PqCGw#vQt-YctCtrCrSHIayQHK9lz z2q1s}0tg_000IagfIvtBefso~Zr!?NUsI+`DSGDx3l>P{&YkT}<{4+4Av0#okO2b* z6g{`ngN#U4Dt7v*e>j9#fJ)Uk9{~gqKmY**5I_Kd^a@ls;n5S6=4quROP1KH>#X}E zt|OEE`}dcZUw&CutXLuU-FKgyeDcZi^wUop=~za(Q~Az2@5m*WTp~|B^^`1Kx>S1g z>Ln+faDrTL!3EN>V@JnLUVZgd89R2Yyzs&c(!G0k88>d6oOj-N+3zcPlh(O^<>pML z)uobWhjW7waGP71mN}Kol?>*&ihHhJ_XnxAp2g-=GM}3b1hDLOJcU)9g6kpZ@fx zcI7ly_`m}Xl!k@|x%uXsMTc@vJ@r)CWRp$g^wUo-3&05oAP`xBJ=Q*UIk5ndZ5|4S z00IagfB*uX1p*wM^jwC56SbeUdiCmpqt$(kbf@y5gANjXIcb}1wvok)7u%~4Ii1S8 z?z&6XtXU(s-+sG2VyP<==}_*~S6@x1(t+bA0tg_000IagfB*srq)K4&D8k!-r@rrC4Lz4>0ZlVpRs4&_cu8@ z1t=BpNd|)r!GicBtyF+=v z0S9=wyzDu#>Quh@=9_Z;_1B9tGO4xGIh9*)y>(tid-UjGf7b7cbt-jeH+OiqaEaZ# z%=mx+0tg_000IagfB*srR4P#8FxT5|yRFg^S9X3&b7NCY_6eCZX_C~{)s=kdGHZ~4nzUz3ic?&YB` zFG)uwwG*l9+D@A`P1?1~A5a}Sbf~>=$lA4Qr9+2xlOJc!oGH4wO0hUsy=Q%WebG%> z9$fKDIbxGBUW&yz2p|wqfjCU8WU^#LAD$Q#E9m_}sv&Tjyv1b7YBae90R#|;k3fE} zEL!D$aO)ek&&C31hjQP2_qCTK(n*sae)yq1VNxeXI_dc1kGEIk88c>#y!P5_a?d^Y zh;FEoJNoFH%TELlKmY**5I_I{1Q0+V6ai-frRkrc6u$gTYBD-mzF6zd&{0NReMeut zd-u+}ER)d|5ql2g6Hh!LW5c0H7GmyxMSQ<0_P2UmYU7ms8$Y~U+Shk7TI|-< zU&vo>{83&X#^?X(a&1qcb7+aQ1d0#OmDvkIawlgV^`e#%Ct(vKe3B7gt_2q1s}0tg_000PAX zoW*yFeOK+VC{0#uX>M$yH|f%nGu!U9zhNS!3lN79NRWW3y+xQLnV^RvVpWOrWJ!n8 zZL$}WDIFnxniP0;pSI131u!W}UI>&Hpi^1ehi?%;009ILKmY**5I_KdiUsPd;Y59z zOs4bmQ#MxO@uSDJ2q1s}0tg_000IagfItcb){RJxIJny%hy^Iy{Di)iCE^4G5-Z>q z3j5yqdnUHdR1ATz1qg)=Tb%nLfIw&h37E^Wsl_N-RKp+KZARfB*srAbew69EJeKmY**5I_I{1i}+Y_#!;g z(%jfYtZR7B6L&%Y0R#|0009INCb0M3-&~WBSb&7J4Ans(OaX={E7W%4-wtSZRE1Y^ zIsynpUBLPMuC|U5kR1}0!fc`WpuK(&lh(`009IL zKmY**5I_I{1j-7$(RAJEQH=#)bh2#oaRLGeAb7?G^<32^3BF>kU;z%VY`^QBVXBKmdWz1rk1Yl1`=1C&~$x zBlOiLB$p!)kbv_$8c_DevULk>B>dGFF&U*$fbiE8HhRLU?Dh;NteVNKPQp8q($d`6 z#7aELeSWDK0tg_000IakNMPD#yMLWnfCM!tRY4#@0t`_ms4=Mu0tg_000IagfB*sr zL_>g;c%sn`lmY<+5I_I{1Q0*~0R#{TLEw!^JAR*7fJ&elovcKw#JRbXw3tj;0*bE? z2v{KZcQN2wg_1oJD??$9#7*4VIueCTSRvddrLnI_!Wx?DAW&<8gioMk?qsbWFz$~4 z0tg_000IagfB*sr#7uw@$(S`*M2p+?;#CI|3lP!9p;!nYfB*srAb&EhLhCMPk{U6gnaTh2gJ+y+)*A zC5q<6`76J$$ zfB*srAb05+IOLJorEAhnl0j1;!AbKmY**5I_I{1Q2i+nBTAS8^i**`^4oNMknLa zQj`+`1Q0*~0R#|0009JIBark7lyoFRcod!RJi=W=z~&aQ7jS-GVqcMnRlqHjl6^%Y z)=(5DHUe&8h=jk!rU@x10tomNpd;xMF!al}JL0i6VgW*D=Y|L%fB*srAbd)2^3t)tjOb|c-0R#|8 zw*Ye|)7}0?YwyenV-BTJRx*pZfb)A1^J>Jl_-=D5wL;9R47OQI*#~=Pat~6Va84z2 zDTCBbUfzYzPx5M*vKl2k%vD;N8=KfCBxMgZ)kOdS1Q0+VN&<_P@6?4@fGD*HWkCP| z1S%IG?zM6=u0Q|*1Q0*~0R#|0009Kb3TV7*QWK+-No`r`g#ZEwAbPHK_@jnPRTcwCAA0tg_000IagfB*srq)~toN%vN7|Ma*? z!~(ec@C^b8AbK2`QN$0m5JX z=!v-b`OB57<0e4(Yus9l@*;php#YAbNv=-$=009KzB)|}51X|0PY{o>&2s8#os7Szhb}PDwlM^dYZek$& zizK$`shB|lCRQ56j8Rr@?H;2lP}E`q36FJUbh22Za|{6l5I_I{1Q0*~fhq+?FFC3& zu>e)#aak+{7@dq|vr%XS5I_I{1Q0*~0R#|;vH<&pM7ebmRyHSOkZ{+8HEe>a<2<7Y zsuEQRQNS$>HZ9?=ZuJkb3Aj~^1&W114sVzm;~G(P1Q3Y3Kr}`qH}87EzljBi`?Ey( z5kLR|1Q0*~0R#|0pqN0$nmnm5lgV^`KFY@2NgsGziU0x#Abf&xJ<5#$0tf^kknqhX=~M>r%#bAl2q1s}0tg_000Ic4TYy;Cbhm#^YwyGx z67yQqW=O@&oo6i-m8Fug5^#%yjZ+M)TODH65EM360_lo@{odX?+?|YA0JnCF)x$?& zQ!5ag9Rl)xVz>w0SOSb!+S z!@8Bllmhu6U`n7c9+o&*Q?lffb^#_(2Ji%Q@7`VXRW4n3-F0&5rI%(;d;k6SMJGqz zbI(1p`|i8j$A=CbDi>UEfvjD-RyuU(U>}<~bEfp~-@oYO$!uo4K>z^+5I_I{1Q0*~ z0R(~)XtRP`^<^@d&d*2LYBD7!ojZ57KX1G3w$i(IZ~Oi6#~&{jU38I* z88b%CKmUALxNxDgw6w@I*Ibi#Eguj-009ILKmY**su$>g%Btsz#{yKJCs!hXKr#d( z-Kq3KtgGwj9wKAMj+G%phR7CMY#|q3cp-a)c){Zw1Q0*~0R#|0009IL2u8qJfyZR- zh6g+S6fIUA$$tI%i7xqCmF^4j-Ow@2q1s} z0tg_000IakU7*gYpf1E?GHISsNj2FgBr27s90(wQ00IagfB*srAbl0R#|00D(LKoje)DEAJ*HK1`W1MRwb5H|f%)i)^{&mU7r( zhsol_i%VQl*;jNbEA!%P1Q0*~0R#|0009ILK%l&UuEG<X?rildGqGU z%$YM~?b@|+;DHCqs#U8z=1}@HCQz34iN&|hBswNi#-h1A7t(20&lhka0tgrtaGU&C z?PN-$g(I_s3p}-J_b=ib3s5+@k_nZ?AJ;;s;%9Lb0W$)RJ@!~$Mt9tChdoL;fBt+K zHf&hlch!8LQ(27~*C2oZ0tg_000IagfB*s|1nR6+bM$30`N?m^iWPQIHrs47BSoSk zX@rqX5I_I{1Q0*~0R#|0AZ`L}k9W8BaO@`|8-^blx3W9sUAc0l^Va@ZUtcf!a!YM( zZ8CoRc-d>Oy=36Pfo`W&bev9QMO@K7IX6U-@YiU!Rk70LwqLRDIfeiN(GYM8g^fn( zCXiR?L#R-5@@sTqc4-`tNi-(>64dhQ&W>PH#g@U%l$BR>{wa2aH0J1 zkAKWP?CU)pNndDskx_NJvqBhNbPEP43hhvk`P zo{`?YduOll`-YCBU%|9pf9x@xm&835AZ=|%eGxza0R#|0009JQDiGw{N$pVPb}EZi zG-J)%95-&9%$PAl=FFKRTW`H}vD2zPMyIlBIxa*20R#|0009ILKmY**qA8%Et<$GZ zmj@qw(B5xy>C&ZkLA!P9CL3qBNiY!?LkQpKp+SKCRPTaN}iDwa5kPaS-=Wbi@oWlo9sL9yYIevC73pCnv5Pj zI`3FDALvw8qsBD|Abqk4En>mskJ~ zP_aCP(aBgg8-+#y0R#|0009ILKmdVA3)Fb-WY9K@P6jPZ{s!^ z1Q?NwZiBYIaLf~d!~!^T_?SZ(OyfJg!Q@V^2q1s}0tlp2pm5IN@P?`BgrL^36sT*n z=1y8KlgV^`e#*w^q#r%5MF0T=5I_I{1Q0*~0R+-0(9+!4#1Lf~Tb?>2fIwE@t{1=i zmq20xvUlJO0tg_000Ic4ULfHUDCtzD>bGKqQh*YJ*`zg)N)0R#|0009Il5h#p( z&Apa5*jx?XM?ygN2r;}*i1GYCE>+kbTZ)$>{dNz?w_eSl)Js$ns=)g2Di`q9M=zyQ(ViR=5lX) z_hot2f9u!_@;>tcfv^R5Cc_rze&qy`_Ds6XtE}amO1CnVYn!xwpZiRnF4gcpArhfjS61Sw;j_i95kLR|1P};9p!0YC+JRVrAY{oC0R#|0 zpqK!olf_h0c+8nN=r)nEa6)9c$&kgquQZP!o587XkoO?(UwrqZcSb&H&4#f&lfYHeivAGoj2q1s}0tg_000Ic4L_qflsWV(x)BBG2 zC?0bsec*8^0tg_000IagfB*srAdp4@YqeU_OAM7{{MMCQeH`gn0DE(rZrxN*`%wLPj zd`ce;mV!$QpBdB7OVIg8wTY^kQ&~7KGpcn9x?FyqnK=J_?v#E1f#=Bv?dy#)f7!ZP zo?LikB>F9kP8w}mGD83X1Q0*~0R#|0009J2Akfm>*u!c1rolzE1k+TKioNWUq2S%clNh#`-bFPTJ_t)_*uWPuf(%f-4v0$ zaQ$cUg22mJ7#CKHG6Y^pZcw#A*v~`Z^QZ+6y9`xhabYL|VLuOr&t&2KwbF&>gaQ%p zP2A#EtBHXPxTrPFJ}u8fMaT*7ND}K>(bK@m2q1s}0tn;^Jo?sw)*SPGGO}U#k-5it zj{pJ)Ab>!61Q?x6Pg_${1Q0*~0R#|0009IL2upy`$*`ok4+01vfB*srAb2z$?vTGzMe{hhUZ)3i@WVSIbJxeh_jp)A@c*1>XfDVZzjCKu1+ zl|GZTsAU|=SPPe=-ZKvE9fvX|_4x?9UWIL~_b>Oq-C>te?^jsPD3-AnE=jy+&}|*( zzt5er@8AENVD<@F{4@3mN#ZXRbwU6E1Q0*~0R#|0009KzD{$?+Nsm_+3&7mT__j7B zM*sl?5I_I{1Q0*~frtuNVW6TflgV^`e#%Bi(vMz@uJs#=;DnTh8Sd(AA>!P7+v%6Z zNSvaU6Y%>?mdihg?+e4V;#h|uT#}GyVOwgQ3fnu1^(-u7VLb}Jk76AtQ*nW~wq@Zn z7uR|eZ)aKmY**5I`Uy z0tsKQuU00IagfB*srAbF3+tK0I!0H&vXZ5xtiq4a|Z;HBf#9rz^+5I_I{1Q0*~fyfA0^BfKD6Jli;nc5NNYBhy36dl(v*TOIaeY(O5uk#b`>NT9z z>u*b;Ls$l0%a^9}Xp42NjYD*vdfI|#GCEHqZIRm+{BGuI)b@SU+9J+>pF3r5*rri7 zXjfmdVeibCYN7+x$L*!e)8p)U&_LT3$vHC zwzkS4ha4h3d-jy)o_kI{_}~LM>7M+ojvYH@&*cpQ2q1s}0tg_000Iag5Sl>3SK^6mNAjhY zUXu6Tdr#W6YbW3Q<~L>O(xrAsQ9G6c2M)B`f9~A5vh&V6+nq?~ffzAjgdO(!+H0@b z*Ez@I{4?rd2x=R42&b zAAVRi-E>pgci(;G-~ax%{hf9+byPApIiK}Ax3qfoYJ0;;jcayZZs&3w0R#|0009IL zKmY**5U5x{7t9IbRdGRrxghk86hNR zrcJZ2yZ7FEeQD?-J>9x>%f9;b>EkOuE=B+W1Q0*~0R#|00D&kA?7H|CYqlKg0tC}W zp@+PlbIv(3di3a$wb^EyZS2K$^rYOmlJ)iVqTlIiu9|cqp68!`-hS`=XW6o4_FMhV zElr+0Std@LnEhHuDCt;cIe3Er0tg_000IagfB*u11(-YOS1|wj?%lhKzRD#Xjnt0g z;>C+)@4ff7PhY!st-SNjJJPprU;FdWp+n`e%Py1U%a_ZRTW)C|n>ll)bnV(TuLD|m zFU?n9QBB0N2iG#p!>G=dXRvSFV>$WcljY)zFV?R~2q1s}0tg_000IagfB*sk2-Ib)6hOYQ%2Fpy z>dHJTR;;k&WWz2zu?S&{yWfwtSZ9NS`!iztn@vB|unTYN0?;lzpQfKbzxnY@@=W@D zhD=^x_&jRfg|V_G?-$j)lIT$Dzt#P;wNv(no~O$O?K=9&YQ1Z}Y+EC009J2D!}MuN*kVPBY*$`2q1s}0tg_0K!5@X-zS8* zlh(82#59LBcd{_1J&kew0shJw%{nb@m6m!MeK=~ASz6jEE%mI?V`TKUX=tm$Iw~Ol z+imnQGMZUodnK{0qF%>iw|M1Hld%9{|E+HFfpp5=(0xoWb0vYRE@&CbmB&VHvV?Cj2LchBcH!%ok1_dH48$shwp zS_mM300IagfB*srAb@}~0x`ckN#99l_B0BD00IagfB*srAb@pduR`B26@4ZkzRo_$E`HL2PH}wG5I_I{1Q0*~0R-|xpxL>-m!xV7 zkRPR|l<^Uu$*YN_cPqLvO0H*leym0 z$R7a&5I_I{1Q0*~0R#}pH-WKL6_o|lY4YcDhu%V4fPC9YDK7#DAbXJ5na+ z5p?>E2~*j(EnlTAKx5>h{Z177P84}Wt43~N3PiELM3G0BMx-6BKot8+6nR9e7JP1* zoT~?)dv+;$W$)i=Xeb>h-`+Uci4K$n)Z{h09fEHofB*srAb<%j@bwr81hGE_kw%P2)7q5! z&d<|IAcre?W$$17k8ql~Zh6s(X0ALZB!@d0StEb|0tg_000Ic)hrkh4BZku!AU|S7 zDWeyl?_~7m-54`j9 zRL}9tII)Po;FbOOqSHcY<~m1DO}m-v+_`hr=+UFqf&~lInl)?G)TvX|;K73f5n&{r zKp05`0R#|0AnpPa`%Jxpwg7RDGTVm$0tm!cV8ezDs(bhDYTUSSfo~*DUIQm3K?D#$ z009ILKmY**5GZ&72b#H-mzOK~3uB@pX;k%p9^6v1X^WEE(o6{CQlMtz=7j9oClfe$ zlLG?^Jblb?Oze{8Vg`>nx4E8~M!RT^kB* z5r&k7DAd@Lm6fIR$cGQ5b}4^xo*3RW&e3Jt5I_I{1Q0*~0R#|00D%w$xFZKA!$BrHS&l@w- z)b!Y_YRnh;PJ9HKHf^eoKmK^ttQprBT#G8PaV;X-%R_9oTevnyY+Q@T_VN&$?G~=h z5gXScvb{XSX1fJ*nG4LDH&6BL+gDAPFu{KLjW^z~^S}T8yBa)raOMft`UXR~*1}*G z0R#|0zy*Oe|EgI;TL2d#K^YK0009ILNJoIalj$h24gv@ufB*srAbcKQL0s| zR%(}Bc1f9IQBjdf{+m2`a>BZE=FCw?9C3syE-qFF9B_bo`st?=mh&cafplW?!w)~G zQ%*U>#;!-&N$-?_0vy3`OBFJPGIibx$4wYPgU*Px3@8Q`Q?`r64zR*yY9M6?X}ll zYNwrcQUeDLRNs8_O~N|s)~!?LoO6yUEiJX@4i{g1v65#FFl8^$P<$@D@WMLB(#a>E zoRBf`N#Nj34g{!3<^VkxSyoq9t256$Q(bwb=_gUoG;!iYwPwv4|F8D=!Sa9_sm%22 z*Uvtv=GtqoRhL|HiIVY#nHn$9P;CDG`)}(m>&aNp&ph*tt-#9(fHx5vPa>Mv5hF(U z$A}y+*IaXrpYfl6{;5tp@kF)nzWb^L3l^x+qem+_xqkfd$NhDf30~lZ7hX`;Uw^%- ztgKX@e)_5E-o3jz>7y!YOF{`%Klcb$6m)mPP&DO1!t z@4TZ{uU@TAKmBxnIWxftY}l~D9tU!PCDkMsxE_A^VfEN!kExG7`bf2I-P#^AQrY(= zsYHJ9#TV+Wx873Ir%zW>`MK<}%e=+BW+Bi}d?ab9IMzNVn>1-sLXN~IfrB@>5HMGy zm}S!%$uB~|&X~%ge(=Eu zcewqJKKiKMb=O_}*vN`E-+Z&~(xuA|zr%v;1$y`Ht<7C8KOu85Mw`1b`o8<_^FL!I zd;wFb*QP?BGNV8K_(L~u-dvjsrN4aX(xqB{hd%h=1OGE-f)_~st*@!6(e2u`)8?X= zpP9tRv^>(L;$lDh^2;x6Z066O@2~UPYp-c@_gVk=WlqY}Q%^mm%)aO^G(YsV8I1pdnUSmi@;Os@ z0^-}PTQ{}mo_qS4h`ZbcldHxu5gVT-k{M~%D$VqI_UvizKGtq>EU`HD*ke_T7A^d; zNW8xO`fDXm3}y;mK(+!gNfX$P9Xs+n5?y#~PST>qT%1gZg}LBtn}>VHjJ!&>m|nel`K6O%OpYzNo5>Wu zfOKDxt}W(5q-|aH+i$=5nGzqLf|(o&G|OS$WbK%MG~MgpzrXF1_vV{#s;jQL%Jz$> zsHm_dl^>njl^OX^-b;mM)22Uk zcdBE?TPCkkp{YGJYu3#EKj^Jfdl~b=3P^KTX$~&GEz6fLSGV1En<_6aw-;cVHf?HS zNPL1dC$-E4s8nWd=FB&4x#bo$YSgHD%VU}lsoDb|)VK1Tci!nQmb)&}u3!3nd6i0k zAu|mM1Ra|OGh~IV1Tu-2^w0CA!w)~)-i5sU^2?QJPH6|H@|N+6Kp_gqiLN)v@gWsU z=|}wHi!XBF(SzX?fiwh$4jt;RB`uWXc-nve{gvFY957&jzl<3Kaw$MXGMD;U=6%Br zHz*nHtG-E^88vp26M%c}xks5kdG-X^^sT5f>Yj|kC!@T{NN`Ns1%ix?xF?TF>5YCU z%VZ)p+wB`OmrcCnIFV+qD^{$qcVTuE3olNMDIeddUqG7X%N2U>xbW7N_uet$ea36F z0&@59po0!l%a$!uC!BDCk|xQfUtYpp$-;?KxBZ@<;$B+-?--Fd$jGkVB=39l=waW= zI>`fC$^UyYnyB}E?|Zz4FCgc3^8cP3S5m1YKH-~_L&E}8DjPO(c8xvu*h9%*SkuCV z3vKf}c^rsLef#!RW}0ez`J5?HAjsH=uRKOXe$c%pE#fYf`9lvq)MiX<68$rM zmrcCnIFY_YUTh>5x8Hud?IUIe9I-`_cuBKA*P6R#>g4MB(sc63Bac+GX3bLGXw+tq z5&Qn?tFLyo*acB8d7Oka(L3+F^K6Xdn2p+q|d7yUoyS@_S;I1ExDM+6s^FjRjZWro%AaH#3x#Ra@&DGgaiJN zRSpHd|NeXZ*=L_=(|1Li=X=?CdB1-BdRwY#)28Vso_InpS+Yc%0Ti^1NNJu+sm)_3 z{8E4W?YH(b^PEF1Bk-AF$F;d5pkIIeb$=aZG8bqlHs%Vj?%K7hHjkaqrqW`)O~pr> zK6`#<#Kxxxb;dlR#g61EaWE&x`uXRd*Ak2K&p+SKS4KoMeUP*{an`1fm^ROAwa3kb z2^0Kvm2mzak<7xFU#>X3x~ff`#?Q7OZB zG8KS;j5jU6EXjYfXV11< z$dS7CFTG^EXwf2l^wCG#n$F&P?`_ZELMaf5CI~$8$RoDaBL6?hb3;D-@WX`Vya``G zo+D*$q1fLD+4tqWX`AO~EVULh;+uVzj$FMw?X=Uh8L`J-&P;Fu@|%$Sn=kL>H%BIU zR)`rm!CncIt1_njq&ByV?K1Blb4$$DB;|^+Bkvw;#U}mtJ~lohxO;r*ZatD>#87Ny_}k!h`?<2q1s}0tg_000IagfIwOTJa95C zrueSZXZSsjd2IpWyOnGi0tg_000IagfB*srge5?wGAvorMF0T=5I_I{1Q0*~0R&PKmY**5I_I{1Q0+V{{^T>=6?*?0t661009ILKmY**5I`VO0Vxr}l zh>V+z5I_I{1Q0*~0R#|0AQAy8m60%$2?7WpfIwIRzx?uxYS*rv8aZ;LpT)d+^Hhr# zE!3=8v;5DPK>z^+5I_Kd-~@`anSx7>ln_7wf!qr`Te5cx+5+T04!J79j2Sc3;K75H zR3^K0>7sh{=%EG;8l=XI8I!9bkuL%WAbeWjvS+YdUpFdv}7Z(=}09%Rx0tg_0K!gHRBqPKoD+CZg0DU< zni{oY#fq406`O$o0tg_0fSUr`opduUlnMa^5I`UUf$HjN)w_3Z+fP#ZOFs0_L$;}F z+qP{pNQG4qKmY-E1s?o-cx&1MxEr5>FWK0tib`sh1-}~rfB*srC^Ln Date: Mon, 4 Aug 2025 11:29:39 -0700 Subject: [PATCH 09/27] rustfmt --- rustfmt.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..c3c8c37 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Crate" From 27320857bca20bc04363409c130773766d066775 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:29:58 -0700 Subject: [PATCH 10/27] decoupled rusterize impl --- src/rusterize_impl.rs | 139 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/rusterize_impl.rs diff --git a/src/rusterize_impl.rs b/src/rusterize_impl.rs new file mode 100644 index 0000000..68814da --- /dev/null +++ b/src/rusterize_impl.rs @@ -0,0 +1,139 @@ +/* +Implementation of rusterize +*/ +use crate::{ + geom::validate::validate_geometries, pixel_functions::PixelFn, prelude::PolarsHandler, + rasterize_geometry::rasterize, structs::raster::RasterInfo, +}; +use geo_types::Geometry; +use ndarray::{Array3, Axis}; +use num_traits::Num; +use polars::prelude::*; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + +#[allow(clippy::too_many_arguments)] +pub fn rusterize_impl( + geometry: Vec, + raster_info: &mut RasterInfo, + pixel_fn: PixelFn, + background: T, + df: Option, + field_name: Option<&str>, + by_name: Option<&str>, + burn_value: T, +) -> (Array3, Vec) +where + T: Num + Copy + PolarsHandler + Literal + Send + Sync, +{ + // validate geometries + let (good_geom, df) = validate_geometries(geometry, df, raster_info); + + // extract `field` and `by` + let casted: DataFrame; + let (field, by) = match df { + None => ( + // case 1: create a dummy `field` + &burn_value.into_column(good_geom.len()), + None, + ), + Some(df) => { + let mut lf = df.lazy(); + match (field_name, by_name) { + (Some(field_name), Some(by_name)) => { + // case 2: both `field` and `by` specified + lf = lf.with_columns([ + col(field_name) + .cast(T::polars_dtype()) + .alias("field_casted"), + col(by_name).cast(DataType::String).alias("by_str"), + ]); + } + (Some(field_name), None) => { + // case 3: only `field` specified + lf = lf.with_column( + col(field_name) + .cast(T::polars_dtype()) + .alias("field_casted"), + ); + } + (None, Some(by_name)) => { + // case 4: only `by` specified + lf = lf.with_columns([ + lit(burn_value).alias("field_casted"), // dummy `field` + col(by_name).cast(DataType::String).alias("by_str"), + ]); + } + (None, None) => { + // case 5: neither `field` nor `by` specified + lf = lf.with_columns([lit(burn_value).alias("field_casted")]) + } + } + + // collect casted dataframe + casted = lf.collect().unwrap(); + + ( + casted.column("field_casted").unwrap(), + casted.column("by_str").ok().and_then(|col| col.str().ok()), + ) + } + }; + + // main + let mut raster: Array3; + let mut band_names: Vec = vec![String::from("band1")]; + match by { + Some(by) => { + // get groups + let groups = by.group_tuples(true, false).expect("No groups found!"); + let n_groups = groups.len(); + let group_idx = groups.into_idx(); + + // multiband raster + raster = raster_info.build_raster(n_groups, background); + raster + .outer_iter_mut() + .into_par_iter() + .zip(group_idx.into_par_iter()) + .map(|(mut band, (group_idx, idxs))| { + // rasterize polygons + for &i in idxs.iter() { + if let (Some(fv), Some(geom)) = { + let anyvalue = field.get(i as usize).unwrap(); + (T::from_anyvalue(anyvalue), good_geom.get(i as usize)) + } { + // process only non-empty field values + rasterize(raster_info, geom, &fv, &mut band, &pixel_fn, &background); + } + } + // band name + by.get(group_idx as usize).unwrap().to_string() + }) + .collect_into_vec(&mut band_names) + } + None => { + // singleband raster + raster = raster_info.build_raster(1, background); + + // rasterize polygons + field + .phys_iter() + .zip(good_geom) + .for_each(|(field_value, geom)| { + if let Some(fv) = T::from_anyvalue(field_value) { + // process only non-empty field values + rasterize( + raster_info, + &geom, + &fv, + &mut raster.index_axis_mut(Axis(0), 0), + &pixel_fn, + &background, + ) + } + }); + } + } + + (raster, band_names) +} From 46c83238aac6334213b76014d4e2966aca3ef679 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:30:18 -0700 Subject: [PATCH 11/27] traits to handle dtype runtime polymorphism --- src/prelude.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/prelude.rs diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..11139ea --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,116 @@ +/* +Traits to handle dtype runtime polymorphism + */ +use polars::prelude::*; +use std::ops::AddAssign; + +// handle polars dtypes and conversions +pub trait PolarsHandler: Literal + Send + Sync { + fn polars_dtype() -> DataType; + fn from_anyvalue(val: AnyValue) -> Option + where + Self: Sized; + fn into_column(self, len: usize) -> Column; +} + +macro_rules! impl_polars_handler { + ($($t:ty => { + dtype: $dtype:expr, + anyvalue: $anyvalue:pat => $extract:expr, + }),* $(,)?) => { + $( + impl PolarsHandler for $t { + fn polars_dtype() -> DataType { + $dtype + } + + fn from_anyvalue(val: AnyValue) -> Option { + match val { + $anyvalue => Some($extract), + _ => None, + } + } + + fn into_column(self, len: usize) -> Column { + Column::new("field_casted".into(), vec![self; len]) + } + } + )* + }; +} + +impl_polars_handler! { + f64 => { + dtype: DataType::Float64, + anyvalue: AnyValue::Float64(v) => v, + }, + f32 => { + dtype: DataType::Float32, + anyvalue: AnyValue::Float32(v) => v, + }, + u8 => { + dtype: DataType::UInt8, + anyvalue: AnyValue::UInt8(v) => v, + }, + i8 => { + dtype: DataType::Int8, + anyvalue: AnyValue::Int8(v) => v, + }, + u16 => { + dtype: DataType::UInt16, + anyvalue: AnyValue::UInt16(v) => v, + }, + i16 => { + dtype: DataType::Int16, + anyvalue: AnyValue::Int16(v) => v, + }, + u32 => { + dtype: DataType::UInt32, + anyvalue: AnyValue::UInt32(v) => v, + }, + i32 => { + dtype: DataType::Int32, + anyvalue: AnyValue::Int32(v) => v, + }, + u64 => { + dtype: DataType::UInt64, + anyvalue: AnyValue::UInt64(v) => v, + }, + i64 => { + dtype: DataType::Int64, + anyvalue: AnyValue::Int64(v) => v, + }, +} + +// handle NaN check for dtype that don't have it +pub trait NaNAware { + fn is_nan(&self) -> bool; +} + +impl NaNAware for f32 { + fn is_nan(&self) -> bool { + f32::is_nan(*self) + } +} + +impl NaNAware for f64 { + fn is_nan(&self) -> bool { + f64::is_nan(*self) + } +} + +macro_rules! impl_maybe_nan_for_int { + ($($t:ty),*) => { + $(impl NaNAware for $t { + fn is_nan(&self) -> bool { + false + } + })* + }; +} + +impl_maybe_nan_for_int!(u8, u16, u32, u64, i8, i16, i32, i64); + +// super trait to group all pixel operations +pub trait PixelOps: AddAssign + PartialOrd + NaNAware + Sized {} +impl PixelOps for T {} From d2d7ba1a111b7fa78d2d301a451ec248c1de68fa Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:30:26 -0700 Subject: [PATCH 12/27] update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 511e850..71aa478 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ tests/ # README quarto stuff README_files + +# uv +uv.lock From f14fd09060dc33a32bbbc906ff336424ff8fae7a Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 11:31:00 -0700 Subject: [PATCH 13/27] update README --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d2e987b..566fa8f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ High performance rasterization tool for Python built in Rust. This repository stems from the [fasterize](https://github.com/ecohealthalliance/fasterize.git) package built in C++ for R and ports parts of the logics into Python with a Rust backend, in addition to some useful improvements. -**rusterize** is designed to work on *(multi)polygons* and *(multi)linestrings*. Functionally, it takes an input [geopandas](https://geopandas.org/en/stable/) dataframe and returns a [xarray](https://docs.xarray.dev/en/stable/). +**rusterize** is designed to work on *(multi)polygons* and *(multi)linestrings*, even when they are nested inside complex geometry collections. Functionally, it takes an input [geopandas](https://geopandas.org/en/stable/) dataframe and returns a [xarray](https://docs.xarray.dev/en/stable/). # Installation @@ -28,7 +28,7 @@ git clone https://github.com//rusterize.git cd rusterize # Install the Rust nightly toolchain -rustup toolchain install nightly-2025-01-05 +rustup toolchain install nightly-2025-07-01 # Install maturin pip install maturin @@ -42,7 +42,7 @@ maturin develop --profile dist-release This package has a simple API: ``` python -from rusterize.core import rusterize +from rusterize import rusterize # gdf = @@ -50,21 +50,25 @@ from rusterize.core import rusterize rusterize(gdf, res=(30, 30), out_shape=(10, 10) - extent=(0, 300, 0, 300) + extent=(0, 10, 10, 20) field="field", by="by", + burn=None, fun="sum", - background=0) + background=0, + dtype="uint8") ``` - `gdf`: geopandas dataframe to rasterize -- `res`: tuple of (xres, yres) for desired resolution (default: `None`) -- `out_shape`: tuple of (nrows, ncols) for desired output shape (default: `None`) -- `extent`: tuple of (xmin, ymin, xmax, ymax) for desired output extent (default: `None`) -- `field`: field to rasterize. (default: `None` -> a value of `1` is rasterized). -- `by`: column to rasterize. Assigns each group to a band in the stack. Values are taken from `field`. (default: `None` -> singleband raster) +- `res`: (xres, yres) for desired resolution (default: `None`) +- `out_shape`: (nrows, ncols) for desired output shape (default: `None`) +- `extent`: (xmin, ymin, xmax, ymax) for desired output extent (default: `None`) +- `field`: column to rasterize. Mutually exclusive with `burn`. (default: `None` -> a value of `1` is rasterized) +- `by`: column for grouping. Assign each group to a band in the stack. Values are taken from `field` if specified, else `burn` is rasterized. (default: `None` -> singleband raster) +- `burn`: a single value to burn. Mutually exclusive with `field`. (default: `None`). If no field is found in `gdf` or if `field` is `None`, then `burn=1` - `fun`: pixel function to use when multiple values overlap. Available options are `sum`, `first`, `last`, `min`, `max`, `count`, or `any`. (default: `last`) -- `background`: background value in final raster. (default: `np.nan`) +- `background`: background value in final raster. (default: `np.nan`). A `None` value corresponds to the default of the specified dtype. An illegal value for a dtype will be replaced with the default of that dtype. For example, a `background=np.nan` for `dtype="uint8"` will become `background=0`, where `0` is the default for `uint8`. +- `dtype`: dtype of the final raster. Possible values are `uint8`, `uint16`, `uint32`, `uint64`, `int8`, `int16`, `int32`, `int64`, `float32`, `float64` (default: `float64`) Note that control over the desired extent is not as strict as for resolution and shape. That is, when resolution, output shape, and extent are specified, priority is given to resolution and shape. @@ -79,7 +83,7 @@ returns a dictionary that is converted to a xarray on the Python side for simpliicty. ``` python -from rusterize.core import rusterize +from rusterize import rusterize import geopandas as gpd from shapely import wkt import matplotlib.pyplot as plt @@ -89,7 +93,8 @@ geoms = [ "POLYGON ((-180 -20, -140 55, 10 0, -140 -60, -180 -20), (-150 -20, -100 -10, -110 20, -150 -20))", "POLYGON ((-10 0, 140 60, 160 0, 140 -55, -10 0))", "POLYGON ((-125 0, 0 60, 40 5, 15 -45, -125 0))", - "MULTILINESTRING ((-180 -70, -140 -50), (-140 -50, -100 -70), (-100 -70, -60 -50), (-60 -50, -20 -70), (-20 -70, 20 -50), (20 -50, 60 -70), (60 -70, 100 -50), (100 -50, 140 -70), (140 -70, 180 -50))" + "MULTILINESTRING ((-180 -70, -140 -50), (-140 -50, -100 -70), (-100 -70, -60 -50), (-60 -50, -20 -70), (-20 -70, 20 -50), (20 -50, 60 -70), (60 -70, 100 -50), (100 -50, 140 -70), (140 -70, 180 -50))", + "GEOMETRYCOLLECTION (POINT (50 -40), POLYGON ((75 -40, 75 -30, 100 -30, 100 -40, 75 -40)), LINESTRING (80 -40, 100 0), GEOMETRYCOLLECTION (POLYGON ((100 20, 100 30, 110 30, 110 20, 100 20))))" ] # Convert WKT strings to Shapely geometries @@ -103,7 +108,7 @@ output = rusterize( gdf, res=(1, 1), field="value", - fun="sum" + fun="sum", ).squeeze() # plot it @@ -119,7 +124,7 @@ plt.show() **rusterize** is fast! Let’s try it on small and large datasets. ``` python -from rusterize.core import rusterize +from rusterize import rusterize import geopandas as gpd import requests import zipfile From e724c4ce994c4460291bebfad02682e2a4a2770f Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 13:34:05 -0700 Subject: [PATCH 14/27] update README --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 566fa8f..97c8aaa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ High performance rasterization tool for Python built in Rust. This repository stems from the [fasterize](https://github.com/ecohealthalliance/fasterize.git) package built in C++ -for R and ports parts of the logics into Python with a Rust backend, in addition to some useful improvements. +for R and ports parts of the logics into Python with a Rust backend, in addition to some useful improvements (see [API](#API)). **rusterize** is designed to work on *(multi)polygons* and *(multi)linestrings*, even when they are nested inside complex geometry collections. Functionally, it takes an input [geopandas](https://geopandas.org/en/stable/) dataframe and returns a [xarray](https://docs.xarray.dev/en/stable/). @@ -28,7 +28,7 @@ git clone https://github.com//rusterize.git cd rusterize # Install the Rust nightly toolchain -rustup toolchain install nightly-2025-07-01 +rustup toolchain install nightly-2025-07-31 # Install maturin pip install maturin @@ -159,11 +159,10 @@ pytest --benchmark-min-rounds=20 --benchmark-time-unit='s' --------------------------------------------- benchmark: 1 tests -------------------------------------------- Name (time in s) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations ------------------------------------------------------------------------------------------------------------- -rusterize_large 1.6430 1.9249 1.7442 0.1024 1.6878 0.1974 6;0 0.5733 20 1 -rusterize_small 0.0912 0.1194 0.1014 0.0113 0.0953 0.0223 7;0 9.8633 20 1 +rusterize_small 0.0791 0.0899 0.0812 0.0027 0.0803 0.0020 2;2 12.3214 20 1 +rusterize_large 1.379545 1.4474 1.4006 0.0178 1.3966 0.0214 5;1 0.7140 20 1 ------------------------------------------------------------------------------------------------------------- ``` - And fasterize: ``` r library(sf) @@ -186,9 +185,9 @@ microbenchmark( ``` ``` Unit: seconds - expr min lq mean median uq max neval - fasterize_large 9.9450280 10.6674467 10.8632224 10.9182963 11.1943478 11.3768210 20 - fasterize_small 0.4906411 0.5140836 0.5581061 0.5320919 0.5603512 0.8750579 20 + expr min lq mean median uq max neval + fasterize_small 0.4741043 0.4926114 0.5191707 0.5193289 0.536741 0.5859029 20 + fasterize_large 9.2199426 10.3595465 10.6653139 10.5369429 11.025771 11.7944567 20 ``` And on an even larger datasets? Here we use a layer from the province of Quebec, Canada representing ~2M polygons of forest stands, rasterized at 30 meters (20 rounds) with no field value and pixel function `any`. The comparison with `gdal_rasterize` was run with `hyperfine --runs 20 "gdal_rasterize -tr 30 30 -burn 1 "`. ``` @@ -196,7 +195,7 @@ And on an even larger datasets? Here we use a layer from the province of Quebec, --------------------------------------------- benchmark: 1 tests -------------------------------------------- Name (time in s) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations ------------------------------------------------------------------------------------------------------------- -rusterize 6.7270 7.0098 6.7824 0.0646 6.7686 0.0266 2;2 0.1474 20 1 +rusterize 5.9331 7.2308 6.1302 0.3183 5.9903 0.1736 2;4 0.1631 20 1 ------------------------------------------------------------------------------------------------------------- # fasterize @@ -205,8 +204,8 @@ Unit: seconds fasterize 157.4734 177.2055 194.3222 194.6455 213.9195 230.6504 20 # gdal_rasterize (CLI) - read from fast drive, write to fast drive -Time (mean ± σ): 5.801 s ± 0.124 s [User: 4.381 s, System: 1.396 s] -Range (min … max): 5.649 s … 6.023 s 20 runs +Time (mean ± σ): 5.495 s ± 0.038 s [User: 4.268 s, System: 1.225 s] +Range (min … max): 5.452 s … 5.623 s 20 runs ``` In terms of (multi)line rasterization speed, here's a benchmark against `gdal_rasterize` using a layer from the province of Quebec, Canada, representing a subset of the road network for a total of ~535K multilinestrings. ``` @@ -223,11 +222,11 @@ Range (min … max): 8.658 s … 8.874 s 20 runs ``` # Comparison with other tools -While **rusterize** is fast, there are other fast alternatives out there, including `GDAL`, `rasterio` and `geocube`. However, **rusterize** allows for a seamless, Rust-native processing with similar or lower memory footprint that doesn't require you to leave Python, and returns the geoinformation you need for downstream processing with ample control over resolution, shape, and extent. +While **rusterize** is fast, there are other fast alternatives out there, including `GDAL`, `rasterio` and `geocube`. However, **rusterize** allows for a seamless, Rust-native processing with similar or lower memory footprint that doesn't require you to leave Python, and returns the geoinformation you need for downstream processing with ample control over resolution, shape, extent, and data type. The following is a time comparison on a single run on the same forest stands dataset used earlier. ``` -rusterize: 6.7 sec +rusterize: 5.9 sec rasterio: 68 sec (but no spatial information) fasterize: 157 sec (including raster creation) geocube: 260 sec (larger memory footprint) From bfeb518c54527509de8920aad729d8e5f547e94c Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 13:56:52 -0700 Subject: [PATCH 15/27] remove support for numpy array in res, out_shape, and extent --- python/rusterize/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/python/rusterize/__init__.py b/python/rusterize/__init__.py index 43a6ea8..bb5825d 100644 --- a/python/rusterize/__init__.py +++ b/python/rusterize/__init__.py @@ -16,9 +16,9 @@ def rusterize( gdf: GeoDataFrame, - res: Tuple | List | np.ndarray | None = None, - out_shape: Tuple | List | np.ndarray | None = None, - extent: Tuple | List | np.ndarray = None, + res: Tuple | List | None = None, + out_shape: Tuple | List | None = None, + extent: Tuple | List | None = None, field: str | None = None, by: str | None = None, burn: int | float | None = None, @@ -57,12 +57,12 @@ def rusterize( # type checks if not isinstance(gdf, GeoDataFrame): raise TypeError("`gdf` must be a geopandas dataframe.") - if not isinstance(res, (tuple, list, np.ndarray, NoneType)): - raise TypeError("`resolution` must be a tuple, list, or numpy array of (x, y).") - if not isinstance(out_shape, (tuple, list, np.ndarray, NoneType)): - raise TypeError("`out_shape` must be a tuple, list, or numpy array of (nrows, ncols).") - if not isinstance(extent, (tuple, list, np.ndarray, NoneType)): - raise TypeError("`extent` must be a tuple, list, or numpy array of (xmin, ymin, xmax, ymax).") + if not isinstance(res, (tuple, list, NoneType)): + raise TypeError("`resolution` must be a tuple or list of (x, y).") + if not isinstance(out_shape, (tuple, list, NoneType)): + raise TypeError("`out_shape` must be a tuple or list of (nrows, ncols).") + if not isinstance(extent, (tuple, list, NoneType)): + raise TypeError("`extent` must be a tuple or list of (xmin, ymin, xmax, ymax).") if not isinstance(field, (str, NoneType)): raise TypeError("`field` must be a string column name.") if not isinstance(by, (str, NoneType)): From 1c56ce75ef430fc431899c631f4873d27d932ba9 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 20:17:55 -0700 Subject: [PATCH 16/27] fixed import for `burn` argument from Python --- src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5485a54..2395be0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ use crate::{ rusterize_impl::rusterize_impl, }; use geo_types::Geometry; -use num_traits::{Num, NumCast}; +use num_traits::Num; use numpy::Element; use polars::prelude::DataFrame; use pyo3::{ @@ -40,15 +40,17 @@ fn execute_rusterize<'py, T>( df: Option, pyfield: Option<&str>, pyby: Option<&str>, - pyburn: Option, + pyburn: Option<&Bound<'py, PyAny>>, ) -> PyResult> where - T: Num + NumCast + Copy + PixelOps + PolarsHandler + FromPyObject<'py> + Element + Default, + T: Num + Copy + PixelOps + PolarsHandler + FromPyObject<'py> + Element + Default, { let background = pybackground .and_then(|inner| inner.extract::().ok()) .unwrap_or_default(); - let burn = pyburn.and_then(|v| T::from(v)).unwrap_or(T::one()); + let burn = pyburn + .and_then(|inner| inner.extract::().ok()) + .unwrap_or(T::one()); let pixel_fn = set_pixel_function::(pypixel_fn); // rusterize @@ -80,7 +82,7 @@ fn rusterize_py<'py>( pydf: Option, pyfield: Option<&str>, pyby: Option<&str>, - pyburn: Option, + pyburn: Option<&Bound<'py, PyAny>>, pybackground: Option<&Bound<'py, PyAny>>, pydtype: &str, ) -> PyResult> { From bb7b784e1aea560fcfe7d148b0a28b7c99388228 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Mon, 4 Aug 2025 20:18:23 -0700 Subject: [PATCH 17/27] properly handle error when `field` is not in `gdf` --- python/rusterize/__init__.py | 185 ++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 91 deletions(-) diff --git a/python/rusterize/__init__.py b/python/rusterize/__init__.py index bb5825d..0192e8f 100644 --- a/python/rusterize/__init__.py +++ b/python/rusterize/__init__.py @@ -15,103 +15,106 @@ def rusterize( - gdf: GeoDataFrame, - res: Tuple | List | None = None, - out_shape: Tuple | List | None = None, - extent: Tuple | List | None = None, - field: str | None = None, - by: str | None = None, - burn: int | float | None = None, - fun: str = "last", - background: int | float | None = np.nan, - dtype: str = "float64", + gdf: GeoDataFrame, + res: Tuple | List | None = None, + out_shape: Tuple | List | None = None, + extent: Tuple | List | None = None, + field: str | None = None, + by: str | None = None, + burn: int | float | None = None, + fun: str = "last", + background: int | float | None = np.nan, + dtype: str = "float64", ) -> DataArray: - """ - Fast geopandas rasterization into xarray.DataArray + """ + Fast geopandas rasterization into xarray.DataArray - Args: - :param gdf: geopandas dataframe to rasterize. - :param res: (xres, yres) for rasterized data. - :param out_shape: (nrows, ncols) for regularized output shape. - :param extent: (xmin, xmax, ymin, ymax) for regularized extent. - :param field: field to rasterize, mutually exclusive with `burn`. Default is None. - :param by: column to rasterize, assigns each unique value to a layer in the stack based on field. Default is None. - :param burn: burn a value onto the raster, mutually exclusive with `field`. Default is None. - :param fun: pixel function to use. Available options are `sum`, `first`, `last`, `min`, `max`, `count`, or `any`. Default is `last`. - :param background: background value in final raster. Default is np.nan. - :param dtype: specify the output dtype. Default is `float64`. + Args: + :param gdf: geopandas dataframe to rasterize. + :param res: (xres, yres) for rasterized data. + :param out_shape: (nrows, ncols) for regularized output shape. + :param extent: (xmin, xmax, ymin, ymax) for regularized extent. + :param field: field to rasterize, mutually exclusive with `burn`. Default is None. + :param by: column to rasterize, assigns each unique value to a layer in the stack based on field. Default is None. + :param burn: burn a value onto the raster, mutually exclusive with `field`. Default is None. + :param fun: pixel function to use. Available options are `sum`, `first`, `last`, `min`, `max`, `count`, or `any`. Default is `last`. + :param background: background value in final raster. Default is np.nan. + :param dtype: specify the output dtype. Default is `float64`. - Returns: - Rasterized xarray.DataArray. + Returns: + Rasterized xarray.DataArray. - Notes: - When any of `res`, `out_shape`, or `extent` is not provided, it is inferred from the other arguments when applicable. - Unless `extent` is specified, a half-pixel buffer is applied to avoid missing points on the border. - The logics dictating the final spatial properties of the rasterized geometries follow those of GDAL. - - If `field` is not in `gdf`, then a default `burn` value of 1 is rasterized. - - A `None` value for `dtype` corresponds to the default of that dtype. An illegal value for a dtype will be replaced with the default of - that dtype. For example, a `background=np.nan` for `dtype="uint8"` will become `background=0`, where `0` is the default for `uint8`. - """ - # type checks - if not isinstance(gdf, GeoDataFrame): - raise TypeError("`gdf` must be a geopandas dataframe.") - if not isinstance(res, (tuple, list, NoneType)): - raise TypeError("`resolution` must be a tuple or list of (x, y).") - if not isinstance(out_shape, (tuple, list, NoneType)): - raise TypeError("`out_shape` must be a tuple or list of (nrows, ncols).") - if not isinstance(extent, (tuple, list, NoneType)): - raise TypeError("`extent` must be a tuple or list of (xmin, ymin, xmax, ymax).") - if not isinstance(field, (str, NoneType)): - raise TypeError("`field` must be a string column name.") - if not isinstance(by, (str, NoneType)): - raise TypeError("`by` must be a string column name.") - if not isinstance(burn, (int, float, NoneType)): - raise TypeError("`burn` must be an integer or float.") - if not isinstance(fun, str): - raise TypeError("`pixel_fn` must be one of sum, first, last, min, max, count, or any.") - if not isinstance(background, (int, float, NoneType)): - raise TypeError("`background` must be integer, float, or None.") - if not isinstance(dtype, str): - raise TypeError("`dtype` must be a one of uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64") + Notes: + When any of `res`, `out_shape`, or `extent` is not provided, it is inferred from the other arguments when applicable. + Unless `extent` is specified, a half-pixel buffer is applied to avoid missing points on the border. + The logics dictating the final spatial properties of the rasterized geometries follow those of GDAL. + + If `field` is not in `gdf`, then a default `burn` value of 1 is rasterized. + + A `None` value for `dtype` corresponds to the default of that dtype. An illegal value for a dtype will be replaced with the default of + that dtype. For example, a `background=np.nan` for `dtype="uint8"` will become `background=0`, where `0` is the default for `uint8`. + """ + # type checks + if not isinstance(gdf, GeoDataFrame): + raise TypeError("`gdf` must be a geopandas dataframe.") + if not isinstance(res, (tuple, list, NoneType)): + raise TypeError("`resolution` must be a tuple or list of (x, y).") + if not isinstance(out_shape, (tuple, list, NoneType)): + raise TypeError("`out_shape` must be a tuple or list of (nrows, ncols).") + if not isinstance(extent, (tuple, list, NoneType)): + raise TypeError("`extent` must be a tuple or list of (xmin, ymin, xmax, ymax).") + if not isinstance(field, (str, NoneType)): + raise TypeError("`field` must be a string column name.") + if not isinstance(by, (str, NoneType)): + raise TypeError("`by` must be a string column name.") + if not isinstance(burn, (int, float, NoneType)): + raise TypeError("`burn` must be an integer or float.") + if not isinstance(fun, str): + raise TypeError("`pixel_fn` must be one of sum, first, last, min, max, count, or any.") + if not isinstance(background, (int, float, NoneType)): + raise TypeError("`background` must be integer, float, or None.") + if not isinstance(dtype, str): + raise TypeError("`dtype` must be a one of uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64") - # value checks - if not res and not out_shape and not extent: - raise ValueError("One of `res`, `out_shape`, or `extent` must be provided.") - if extent and not res and not out_shape: - raise ValueError("Must also specify `res` or `out_shape` with extent.") - if res and (len(res) != 2 or any(r <= 0 for r in res) or any(not isinstance(r, (int, float)) for r in res)): - raise ValueError("Resolution must be 2 positive numbers.") - if out_shape and (len(out_shape) != 2 or any(s <= 0 for s in out_shape) or any(not isinstance(s, int) for s in out_shape)): - raise ValueError("Output shape must be 2 positive integers.") - if extent and len(extent) != 4: - raise ValueError("Extent must be 4 numbers (xmin, ymin, xmax, ymax).") - if field and burn: - raise ValueError("Only one of `field` or `burn` can be specified.") + # value checks + if not res and not out_shape and not extent: + raise ValueError("One of `res`, `out_shape`, or `extent` must be provided.") + if extent and not res and not out_shape: + raise ValueError("Must also specify `res` or `out_shape` with extent.") + if res and (len(res) != 2 or any(r <= 0 for r in res) or any(not isinstance(r, (int, float)) for r in res)): + raise ValueError("Resolution must be 2 positive numbers.") + if out_shape and (len(out_shape) != 2 or any(s <= 0 for s in out_shape) or any(not isinstance(s, int) for s in out_shape)): + raise ValueError("Output shape must be 2 positive integers.") + if extent and len(extent) != 4: + raise ValueError("Extent must be 4 numbers (xmin, ymin, xmax, ymax).") + if field and burn: + raise ValueError("Only one of `field` or `burn` can be specified.") - # defaults - _res = res if res else (0, 0) - _shape = out_shape if out_shape else (0, 0) - (_bounds, _has_extent) = (extent, True) if extent else (gdf.total_bounds, False) + # defaults + _res = res if res else (0, 0) + _shape = out_shape if out_shape else (0, 0) + (_bounds, _has_extent) = (extent, True) if extent else (gdf.total_bounds, False) - # RasterInfo - raster_info = { - "nrows": _shape[0], - "ncols": _shape[1], - "xmin": _bounds[0], - "ymin": _bounds[1], - "xmax": _bounds[2], - "ymax": _bounds[3], - "xres": _res[0], - "yres": _res[1], - "has_extent": _has_extent, - } + # RasterInfo + raster_info = { + "nrows": _shape[0], + "ncols": _shape[1], + "xmin": _bounds[0], + "ymin": _bounds[1], + "xmax": _bounds[2], + "ymax": _bounds[3], + "xres": _res[0], + "yres": _res[1], + "has_extent": _has_extent, + } - # extract columns of interest and convert to polars - cols = list(set([col for col in (field, by) if col])) - df = pl.from_pandas(gdf[cols]) if cols else None + # extract columns of interest and convert to polars + cols = list(set([col for col in (field, by) if col])) + try: + df = pl.from_pandas(gdf[cols]) if cols else None + except KeyError as e: + raise KeyError("Column not found in GeoDataFrame") from e - # rusterize - r = _rusterize(gdf.geometry, raster_info, fun, df, field, by, burn, background, dtype) - return DataArray.from_dict(r).rio.write_crs(gdf.crs, inplace=True) + # rusterize + r = _rusterize(gdf.geometry, raster_info, fun, df, field, by, burn, background, dtype) + return DataArray.from_dict(r).rio.write_crs(gdf.crs, inplace=True) From ba9ada2df3904e35c7bf1150b1165f81ade6758d Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 12:55:55 -0700 Subject: [PATCH 18/27] potential fix: activaet rustflags only for x86_64 --- .github/workflows/test-compile.yml | 35 +++++++++++++++++++++--------- Cargo.toml | 4 ++-- pyproject.toml | 4 ++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index b88f6f8..fe10d47 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -17,14 +17,10 @@ jobs: platform: - runner: ubuntu-22.04 target: x86_64 - # - runner: ubuntu-22.04 - # target: x86 - runner: ubuntu-22.04 target: aarch64 - runner: ubuntu-22.04 target: armv7 - # - runner: ubuntu-22.04 - # target: s390x - runner: ubuntu-22.04 target: ppc64le steps: @@ -32,6 +28,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -53,8 +54,6 @@ jobs: platform: - runner: ubuntu-22.04 target: x86_64 - # - runner: ubuntu-22.04 - # target: x86 - runner: ubuntu-22.04 target: aarch64 - runner: ubuntu-22.04 @@ -64,6 +63,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -84,15 +88,20 @@ jobs: matrix: platform: - runner: windows-latest - target: x64 - # - runner: windows-latest - # target: x86 + target: x86_64 + - runner: windows-latest + target: aarch64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x architecture: ${{ matrix.platform.target }} + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -120,6 +129,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -145,4 +159,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: wheels-sdist - path: dist \ No newline at end of file + path: dist + diff --git a/Cargo.toml b/Cargo.toml index 38efd9c..872b161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,6 @@ tikv-jemallocator = { version = "*", features = ["disable_initial_exec_tls"] } [profile.dist-release] inherits = "release" -rustflags = ["-C", "target-feature=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe"] codegen-units = 1 -lto = true +debug = false +lto = "fat" diff --git a/pyproject.toml b/pyproject.toml index 0096260..7641904 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "High performance rasterization tool for Python built in Rust" readme = "README.md" requires-python = ">=3.11" license = {file = "LICENSE"} -keywords = ["fast", "raster", "geopandas", "xarray"] +keywords = ["rust", "fast", "raster", "geometry", "geopandas", "xarray"] author = {name = "Tommaso Trotto", email = "ttrotto@mail.ubc.ca"} classifiers = [ "License :: OSI Approved :: MIT License", @@ -33,4 +33,4 @@ repository = "https://github.com/ttrotto/rusterize" [tool.maturin] python-source = "python" module-name = "rusterize" -include = [{ path = "rust-toolchain.toml", format = "sdist" }] \ No newline at end of file +include = [{ path = "rust-toolchain.toml", format = "sdist" }] From 0608b3c1177e3ee8a5af48ebd86ef5464cb4a48c Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:07:02 -0700 Subject: [PATCH 19/27] still try solving compilation error for ring in linux aarch64 --- .github/workflows/test-compile.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index fe10d47..a42bb44 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -33,6 +33,11 @@ jobs: run: | FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV + - name: Fix problem with ring crate compilation + if: matrix.platform.target == 'aarch64' + run: | + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: From 3b5f7a4ea4bd7705d1063265b614906f9fed83f1 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:15:33 -0700 Subject: [PATCH 20/27] remove CI for windows-latest aarch64 --- .github/workflows/test-compile.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index a42bb44..2f44f79 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -79,7 +79,7 @@ jobs: target: ${{ matrix.platform.target }} args: --profile dist-release --out dist --find-interpreter sccache: "true" - manylinux: musllinux_1_2 + manylinux: auto - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -93,9 +93,7 @@ jobs: matrix: platform: - runner: windows-latest - target: x86_64 - - runner: windows-latest - target: aarch64 + target: x64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From adba5268eed4137e85c70bcfe796773ea6f75eef Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:24:25 -0700 Subject: [PATCH 21/27] try changing manylinux version --- .github/workflows/test-compile.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index 2f44f79..27ee9bc 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -33,18 +33,13 @@ jobs: run: | FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - - name: Fix problem with ring crate compilation - if: matrix.platform.target == 'aarch64' - run: | - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --profile dist-release --out dist --find-interpreter sccache: "true" - manylinux: auto + manylinux: manylinux_2_28 - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -79,7 +74,7 @@ jobs: target: ${{ matrix.platform.target }} args: --profile dist-release --out dist --find-interpreter sccache: "true" - manylinux: auto + manylinux: manylinux_2_28 - name: Upload wheels uses: actions/upload-artifact@v4 with: From 18543319b88981671dfd9e8f848c8f0b8bf56a5c Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:38:52 -0700 Subject: [PATCH 22/27] fixed rustflags for x86_64 and re-included windows-latest aarch64 --- .github/workflows/test-compile.yml | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index 27ee9bc..36c7543 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -28,10 +28,16 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} run: | - FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 @@ -63,10 +69,16 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} run: | - FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 @@ -89,16 +101,24 @@ jobs: platform: - runner: windows-latest target: x64 + - runner: windows-latest + target: aarch64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x architecture: ${{ matrix.platform.target }} + - name: Store RUSTFLAGS in ENV + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} run: | - FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 @@ -127,10 +147,16 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} run: | - FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 From cc3bbdb9aba18d1e0e25284d7b7814b3b19c5d3b Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:41:07 -0700 Subject: [PATCH 23/27] fixed syntax error in workflow --- .github/workflows/test-compile.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index 36c7543..8ff67a5 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -36,7 +36,7 @@ jobs: - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' env: - FEATURES: ${{ steps.features.outputs.features }} + FEATURES: ${{ steps.features.outputs.features }} run: | echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels @@ -77,7 +77,7 @@ jobs: - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' env: - FEATURES: ${{ steps.features.outputs.features }} + FEATURES: ${{ steps.features.outputs.features }} run: | echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels @@ -117,7 +117,7 @@ jobs: - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' env: - FEATURES: ${{ steps.features.outputs.features }} + FEATURES: ${{ steps.features.outputs.features }} run: | echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels @@ -155,7 +155,7 @@ jobs: - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' env: - FEATURES: ${{ steps.features.outputs.features }} + FEATURES: ${{ steps.features.outputs.features }} run: | echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels From 91f77681aebfed3b4234b9fd66010c090e178738 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:44:11 -0700 Subject: [PATCH 24/27] removed once again windows-latest aarch64 :/ --- .github/workflows/test-compile.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index 8ff67a5..e9c3bae 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -101,8 +101,6 @@ jobs: platform: - runner: windows-latest target: x64 - - runner: windows-latest - target: aarch64 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From a379202728108d4caf5225386ced668c874010c7 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:47:07 -0700 Subject: [PATCH 25/27] hopefully fixed workflow for windows --- .github/workflows/test-compile.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index e9c3bae..ce35b67 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -109,9 +109,10 @@ jobs: architecture: ${{ matrix.platform.target }} - name: Store RUSTFLAGS in ENV id: features + shell: pwsh run: | - FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe - echo "features=$FEATURES" >> $GITHUB_OUTPUT + $features = "+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe" + "features=$features" >> $env:GITHUB_OUTPUT - name: Set RUSTFLAGS for x86_64 if: matrix.platform.target == 'x86_64' env: @@ -130,6 +131,7 @@ jobs: name: wheels-windows-${{ matrix.platform.target }} path: dist + macos: runs-on: ${{ matrix.platform.runner }} strategy: From e975875885cfb370f68654f74694a23fafd9579a Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 13:53:34 -0700 Subject: [PATCH 26/27] try again with bash shell and correct architecture for windows --- .github/workflows/test-compile.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-compile.yml b/.github/workflows/test-compile.yml index ce35b67..9f48bb2 100644 --- a/.github/workflows/test-compile.yml +++ b/.github/workflows/test-compile.yml @@ -109,12 +109,12 @@ jobs: architecture: ${{ matrix.platform.target }} - name: Store RUSTFLAGS in ENV id: features - shell: pwsh + shell: bash run: | - $features = "+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe" - "features=$features" >> $env:GITHUB_OUTPUT - - name: Set RUSTFLAGS for x86_64 - if: matrix.platform.target == 'x86_64' + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT + - name: Set RUSTFLAGS for x64 + if: matrix.platform.target == 'x64' env: FEATURES: ${{ steps.features.outputs.features }} run: | From 0c2be36e40f319568571618b124c56ffb2816420 Mon Sep 17 00:00:00 2001 From: ttrotto Date: Tue, 5 Aug 2025 14:45:05 -0700 Subject: [PATCH 27/27] updated workflow with architecture-dependent RUSTFLAGS --- .github/workflows/CI.yml | 81 ++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7f8f142..f6f2bfa 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -16,14 +16,10 @@ jobs: platform: - runner: ubuntu-22.04 target: x86_64 - # - runner: ubuntu-22.04 - # target: x86 - runner: ubuntu-22.04 target: aarch64 - runner: ubuntu-22.04 target: armv7 - # - runner: ubuntu-22.04 - # target: s390x - runner: ubuntu-22.04 target: ppc64le steps: @@ -31,13 +27,25 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV for x86_64 + if: matrix.platform.target == 'x86_64' + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} + run: | + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --profile dist-release --out dist --find-interpreter sccache: "true" - manylinux: auto + manylinux: manylinux_2_28 - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -52,8 +60,6 @@ jobs: platform: - runner: ubuntu-22.04 target: x86_64 - # - runner: ubuntu-22.04 - # target: x86 - runner: ubuntu-22.04 target: aarch64 - runner: ubuntu-22.04 @@ -63,13 +69,25 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV for x86_64 + if: matrix.platform.target == 'x86_64' + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} + run: | + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --profile dist-release --out dist --find-interpreter sccache: "true" - manylinux: musllinux_1_2 + manylinux: manylinux_2_28 - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -84,14 +102,25 @@ jobs: platform: - runner: windows-latest target: x64 - # - runner: windows-latest - # target: x86 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x architecture: ${{ matrix.platform.target }} + - name: Store RUSTFLAGS in ENV for x86_64 + if: matrix.platform.target == 'x86_64' + id: features + shell: bash + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT + - name: Set RUSTFLAGS for x64 + if: matrix.platform.target == 'x64' + env: + FEATURES: ${{ steps.features.outputs.features }} + run: | + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -104,6 +133,7 @@ jobs: name: wheels-windows-${{ matrix.platform.target }} path: dist + macos: runs-on: ${{ matrix.platform.runner }} strategy: @@ -119,6 +149,18 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x + - name: Store RUSTFLAGS in ENV for x86_64 + if: matrix.platform.target == 'x86_64' + id: features + run: | + FEATURES=+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt,+cmpxchg16b,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+pclmulqdq,+movbe + echo "features=$FEATURES" >> $GITHUB_OUTPUT + - name: Set RUSTFLAGS for x86_64 + if: matrix.platform.target == 'x86_64' + env: + FEATURES: ${{ steps.features.outputs.features }} + run: | + echo "RUSTFLAGS=-C target-feature=$FEATURES" >> $GITHUB_ENV - name: Build wheels uses: PyO3/maturin-action@v1 with: @@ -146,22 +188,3 @@ jobs: name: wheels-sdist path: dist - publish-to-pypi: - name: Publish to PyPI - needs: [ linux, musllinux, windows, macos, sdist ] - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/rusterize - permissions: - id-token: write - steps: - - name: Download sdists and wheels - uses: actions/download-artifact@v4 - with: - path: dist - merge-multiple: true - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - verbose: true