diff --git a/src/list/raxos/evolution/.gitignore b/src/list/raxos/evolution/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/src/list/raxos/evolution/.gitignore @@ -0,0 +1 @@ +/target diff --git a/src/list/raxos/evolution/Cargo.lock b/src/list/raxos/evolution/Cargo.lock new file mode 100644 index 0000000..ad2fb4d --- /dev/null +++ b/src/list/raxos/evolution/Cargo.lock @@ -0,0 +1,929 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dwrote" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "evolution" +version = "0.1.0" +dependencies = [ + "plotters", + "rand", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "font-kit" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2845a73bbd781e691ab7c2a028c579727cd254942e8ced57ff73e0eafd60de87" +dependencies = [ + "bitflags 2.6.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-bitmap" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e7f6fb8302456d7c264a94dada86f76d76e1a03e2294ee86ca7da92983b0a6" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e256ff62cee3e03def855c4d4260106d2bb1696fdc01af03e9935b993720a5" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d299e9db34f6623b2a9e86c015d6e173d5f46d64d4b9b8cc46ae8a982a50b04c" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e93f5a5e3c528cda9acb0928c31b2ba868c551cc46e67b778075e34aab9906" +dependencies = [ + "getrandom", + "zerocopy", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[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 = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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 = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" +dependencies = [ + "cstr", + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/list/raxos/evolution/Cargo.toml b/src/list/raxos/evolution/Cargo.toml new file mode 100644 index 0000000..2ba2896 --- /dev/null +++ b/src/list/raxos/evolution/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "evolution" +version = "0.1.0" +edition = "2021" + +[dependencies] +plotters = "0.3.6" +rand = "0.9.0-alpha.2" diff --git a/src/list/raxos/evolution/src/config.rs b/src/list/raxos/evolution/src/config.rs new file mode 100644 index 0000000..42f37f2 --- /dev/null +++ b/src/list/raxos/evolution/src/config.rs @@ -0,0 +1,12 @@ +pub struct Config { + pub n_points_per_scene: usize, + + /// The number of round to evolve. + pub n_round: usize, + + /// Number of variants to spawn for each contour + pub n_spawn: usize, + + /// The probability of moving a point, adding a point, or removing a point + pub variant_weight: (f64, f64, f64), +} diff --git a/src/list/raxos/evolution/src/contour.rs b/src/list/raxos/evolution/src/contour.rs new file mode 100644 index 0000000..3ccd412 --- /dev/null +++ b/src/list/raxos/evolution/src/contour.rs @@ -0,0 +1,277 @@ +use std::fmt; + +use crate::display_slice::DisplaySliceExt; +use crate::point::Point; + +/// The Contour line of all the points that have the same value +/// +/// This is not an accurate line. +#[derive(Clone, Debug)] +pub struct Contour { + pub points: Vec, +} + +impl fmt::Display for Contour { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.points.display_n::<30>())?; + Ok(()) + } +} + +impl Contour { + pub fn new(points: impl IntoIterator) -> Self { + Self { + points: points.into_iter().collect(), + } + } + + /// Update the contour line by adding random points or move the existing points. + /// + /// `prob_move` is the chance of moving a point. + /// `1 - prob_move` is the chance of adding a new point. + pub fn rand_update( + &self, + prob_move: f64, + mut prob_add: f64, + mut prob_remove: f64, + ) -> (Self, String) { + let l = self.points.len(); + + if l <= 3 { + // always move + prob_remove = 0.0; + } + + if l >= 40 { + // always remove + prob_add = 0.0; + } + + // Generate a rand number between 0 and 1 + let rand = rand::random::() * (prob_move + prob_add + prob_remove); + + if rand < prob_move { + // move a point randomly + let i = (rand::random::() as usize) % self.points.len(); + if self.points[i] == Point::new(1f64, 1f64) { + // The unit point should not be moved + return (self.clone(), "Nothing".to_string()); + } + + let (x_left, x_right) = if i == 0 { + let right_sibling = self.points[i + 1].x; + (0.0, right_sibling) + } else if i == l - 1 { + let left_sibling = self.points[i - 1].x; + (left_sibling, (self.points[i].x * 1.5f64)) + } else { + (self.points[i - 1].x, self.points[i + 1].x) + }; + + let (y_low, y_high) = if i == 0 { + let right_sibling = self.points[i + 1].y; + (right_sibling, (self.points[i].y * 1.5f64)) + } else if i == l - 1 { + let last = self.points[i - 1].y; + (0.0, last) + } else { + (self.points[i + 1].y, self.points[i - 1].y) + }; + + let mut points = self.points.clone(); + + let x = rand::random::() * (x_right - x_left) + x_left; + let y = rand::random::() * (y_high - y_low) + y_low; + let p = Point::new(x, y); + + let p0 = points[i]; + points[i] = p; + (Self { points }, format!("Move {i}: from {p0} to {p}")) + } else if rand < prob_move + prob_add { + // Add a new point + + let p1 = Point::new(1f64, 1f64); + let position = self.points.iter().position(|p| *p == p1).unwrap(); + + let i = loop { + let i = (rand::random::() as usize) % (self.points.len() + 1); + if i > position && position < l / 2 || i <= position && position >= l / 2 { + // The unit point should not be moved + continue; + } + + break i; + }; + + // The index of point before which to add new point + + let (x_left, x_right, y_low, y_high) = if i == 0 { + let right = self.points[i]; + (0.0, right.x, right.y, right.y * 1.5) + } else if i == self.points.len() { + let left = self.points[i - 1]; + (left.x, left.x * 1.5, 0.0, left.y) + } else { + let left = self.points[i - 1]; + let right = self.points[i]; + (left.x, right.x, right.y, left.y) + }; + + let x = rand::random::() * (x_right - x_left) + x_left; + let y = rand::random::() * (y_high - y_low) + y_low; + + let mut points = self.points.clone(); + let p = Point::new(x, y); + points.insert(i, p); + (Self { points }, format!("Add {p} before {i}")) + } else { + // remove a point + let i = (rand::random::() as usize) % self.points.len(); + if self.points[i] == Point::new(1f64, 1f64) { + // The unit point should not be removed + return (self.clone(), "Nothing".to_string()); + } + + let mut points = self.points.clone(); + + points.remove(i); + (Self { points }, format!("Remove {i}")) + } + } + + /// Compare if a point is after or before the contour line + pub fn below_eq(&self, p: &Point) -> bool { + let x = self.cross_product_x(p); + + x >= 0f64 + } + + pub fn cross_product_x(&self, p: &Point) -> f64 { + let (p1, p2) = self.find_adjacent_points_for(p); + let a = p2 - p1; + let b = *p - p1; + + a.cross_product(&b) + } + + fn find_adjacent_points_for(&self, p: &Point) -> (Point, Point) { + let first_bigger = self + .points + .iter() + .enumerate() + .filter(|(_i, q)| q.x >= p.x) + .next(); + + if let Some(first) = first_bigger { + let index = first.0; + if index == self.points.len() - 1 { + // p is the last point + (self.points[index - 1], self.points[index]) + } else if index == 0 { + (self.points[0], self.points[1]) + } else { + (self.points[index - 1], self.points[index]) + } + } else { + // p >= all points, use the last two. + let mut it = self.points.iter().rev(); + let last = it.next().unwrap(); + let second_last = it.next().unwrap(); + (*second_last, *last) + } + } + + pub fn validate(&self) { + assert!(self + .points + .iter() + .position(|p| *p == Point::new(1.0, 1.0)) + .is_some()); + + for i in 1..self.points.len() { + assert!(self.points[i - 1].x < self.points[i].x, "{:?}", self.points); + + assert!(self.points[i - 1].y > self.points[i].y, "{:?}", self.points); + } + } +} + +#[cfg(test)] +mod tests { + use crate::contour::Contour; + use crate::point::Point; + + #[test] + fn test_find_adjacent() { + let c = contour([(1, 1), (2, 2), (3, 3)]); + assert_eq!(c.find_adjacent_points_for(&pi(0, 0)), (pi(1, 1), pi(2, 2))); + assert_eq!(c.find_adjacent_points_for(&pi(1, 1)), (pi(1, 1), pi(2, 2))); + assert_eq!(c.find_adjacent_points_for(&pi(1, 2)), (pi(1, 1), pi(2, 2))); + assert_eq!( + c.find_adjacent_points_for(&p(1.5, 2.0)), + (pi(1, 1), pi(2, 2)) + ); + assert_eq!(c.find_adjacent_points_for(&pi(2, 2)), (pi(1, 1), pi(2, 2))); + assert_eq!( + c.find_adjacent_points_for(&p(2.5, 2.0)), + (pi(2, 2), pi(3, 3)) + ); + assert_eq!(c.find_adjacent_points_for(&pi(3, 3)), (pi(2, 2), pi(3, 3))); + assert_eq!(c.find_adjacent_points_for(&pi(4, 4)), (pi(2, 2), pi(3, 3))); + } + + #[test] + fn test_above() { + assert_eq!(contour([(1, 2), (2, 3)]).below_eq(&pi(2, 2)), false); + assert_eq!(contour([(1, 2), (2, 3)]).below_eq(&pi(2, 4)), true); + + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(2, 4)), true); + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(2, 3)), true); + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(2, 2)), false); + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(2, 1)), false); + + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(0, 2)), true); + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(0, 1)), true); + assert_eq!(contour([(1, 2), (3, 4)]).below_eq(&pi(0, 0)), false); + + // 3 + + // | + // 2 + * - * + // | / + // 1 + * + // | + // 0 +---+---+---+---+----> + // 0 1 2 3 4 + let c = contour([(1, 1), (2, 2), (3, 2)]); + assert_eq!(c.below_eq(&pi(0, -1)), false); + assert_eq!(c.below_eq(&pi(0, 0)), true); + assert_eq!(c.below_eq(&pi(0, 1)), true); + assert_eq!(c.below_eq(&pi(1, 0)), false); + assert_eq!(c.below_eq(&pi(1, 1)), true); + assert_eq!(c.below_eq(&pi(1, 2)), true); + + assert_eq!(c.below_eq(&pi(2, 1)), false); + assert_eq!(c.below_eq(&pi(2, 2)), true); + assert_eq!(c.below_eq(&pi(2, 3)), true); + + assert_eq!(c.below_eq(&pi(3, 1)), false); + assert_eq!(c.below_eq(&pi(3, 2)), true); + assert_eq!(c.below_eq(&pi(3, 3)), true); + + assert_eq!(c.below_eq(&pi(4, 1)), false); + assert_eq!(c.below_eq(&pi(4, 2)), true); + assert_eq!(c.below_eq(&pi(4, 3)), true); + } + + fn contour(points: impl IntoIterator) -> Contour { + Contour::new(points.into_iter().map(Point::from)) + } + + fn pi(x: i64, y: i64) -> Point { + Point::from((x as f64, y as f64)) + } + + fn p(x: f64, y: f64) -> Point { + Point::from((x, y)) + } +} diff --git a/src/list/raxos/evolution/src/display_slice.rs b/src/list/raxos/evolution/src/display_slice.rs new file mode 100644 index 0000000..96e18b9 --- /dev/null +++ b/src/list/raxos/evolution/src/display_slice.rs @@ -0,0 +1,83 @@ +use std::fmt; + +/// Implement `Display` for `&[T]` if T is `Display`. +/// +/// It outputs at most `MAX` elements, excluding those from the 5th to the second-to-last one: +/// - `DisplaySlice(&[1,2,3,4,5,6])` outputs: `"[1,2,3,4,...,6]"`. +pub(crate) struct DisplaySlice<'a, T: fmt::Display, const MAX: usize = 5>(pub &'a [T]); + +impl<'a, T: fmt::Display, const MAX: usize> fmt::Display for DisplaySlice<'a, T, MAX> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let slice = self.0; + let len = slice.len(); + + write!(f, "[")?; + + if len > MAX { + for (i, t) in slice[..(MAX - 1)].iter().enumerate() { + if i > 0 { + write!(f, ",")?; + } + + write!(f, "{}", t)?; + } + + write!(f, ",..,")?; + write!(f, "{}", slice.last().unwrap())?; + } else { + for (i, t) in slice.iter().enumerate() { + if i > 0 { + write!(f, ",")?; + } + + write!(f, "{}", t)?; + } + } + + write!(f, "]") + } +} + +pub(crate) trait DisplaySliceExt<'a, T: fmt::Display> { + #[allow(dead_code)] + fn display(&'a self) -> DisplaySlice<'a, T>; + + /// Display at most `MAX` elements. + fn display_n(&'a self) -> DisplaySlice<'a, T, MAX>; +} + +impl DisplaySliceExt<'_, T> for [T] +where + T: fmt::Display, +{ + fn display(&self) -> DisplaySlice { + DisplaySlice(self) + } + + fn display_n(&'_ self) -> DisplaySlice<'_, T, MAX> { + DisplaySlice(self) + } +} + +#[cfg(test)] +mod tests { + use crate::display_slice::DisplaySlice; + + #[test] + fn test_display_slice() { + let a = vec![1, 2, 3, 4]; + assert_eq!("[1,2,3,4]", DisplaySlice::<_>(&a).to_string()); + + let a = vec![1, 2, 3, 4, 5]; + assert_eq!("[1,2,3,4,5]", DisplaySlice::<_>(&a).to_string()); + + let a = vec![1, 2, 3, 4, 5, 6]; + assert_eq!("[1,2,3,4,..,6]", DisplaySlice::<_>(&a).to_string()); + + let a = vec![1, 2, 3, 4, 5, 6, 7]; + assert_eq!("[1,2,3,4,..,7]", DisplaySlice::<_>(&a).to_string()); + + let a = vec![1, 2, 3, 4, 5, 6, 7]; + assert_eq!("[1,..,7]", DisplaySlice::<_, 2>(&a).to_string()); + } +} diff --git a/src/list/raxos/evolution/src/draw.rs b/src/list/raxos/evolution/src/draw.rs new file mode 100644 index 0000000..6528f1b --- /dev/null +++ b/src/list/raxos/evolution/src/draw.rs @@ -0,0 +1,61 @@ +use plotters::backend::BitMapBackend; +use plotters::chart::ChartBuilder; +use plotters::drawing::IntoDrawingArea; +use plotters::element::Circle; +use plotters::prelude::{Color, HSLColor, IntoFont, LineSeries, WHITE}; + +use crate::contour::Contour; +use crate::scene::Scene; + +pub fn draw_contour( + path: impl ToString, + _scene: &Scene, + contours: &[Contour], +) -> Result<(), Box> { + let path = path.to_string(); + + // 创建一个400x400像素的PNG文件 + + // white bg: + let root = BitMapBackend::new(&path, (400, 400)).into_drawing_area(); + root.fill(&WHITE)?; + + // 创建图表上下文 + let mut chart = ChartBuilder::on(&root) + .caption("散点图示例", ("sans-serif", 50).into_font()) + .margin(5) + .x_label_area_size(30) + .y_label_area_size(30) + .build_cartesian_2d(0f64..5f64, 0f64..5f64)?; + + // 配置坐标轴 + chart.configure_mesh().draw()?; + + // // Scene + // let ps = scene + // .points + // .iter() + // .map(|p| Circle::new((p.x, p.y), 3, &BLUE.mix(0.5))); + // chart.draw_series(ps)?; + + let l = contours.len() as f64; + for (i, contour) in contours.iter().enumerate() { + let color = HSLColor(i as f64 / l, 0.8, 0.5).mix(0.5); + + chart.draw_series(LineSeries::new( + contour.points.iter().map(|p| (p.x, p.y)), + &color.mix(0.2), + ))?; + + // Draw contour + let ps = contour + .points + .iter() + .map(|p| Circle::new((p.x, p.y), 1, &color.mix(0.5))); + chart.draw_series(ps)?; + } + + root.present()?; + + Ok(()) +} diff --git a/src/list/raxos/evolution/src/main.rs b/src/list/raxos/evolution/src/main.rs new file mode 100644 index 0000000..05de430 --- /dev/null +++ b/src/list/raxos/evolution/src/main.rs @@ -0,0 +1,170 @@ +use rand::prelude::SliceRandom; + +use config::Config; +use point::Point; +use scene::Scene; + +use crate::contour::Contour; + +pub mod config; +pub mod contour; +pub mod display_slice; +pub mod draw; +pub mod point; +pub mod scene; + +pub struct Evolution { + config: Config, + + scene: Scene, + + /// A normalized by each point of the scene + normalized: Vec, + + /// Best found contour and its conflicts + contours: Vec, +} + +impl Evolution { + pub fn new(config: Config, scene: Scene, build_contour: impl Fn(&Point) -> Contour) -> Self { + let normalized = scene + .points + .iter() + .map(|point| scene.normalize(*point)) + .collect::>(); + + let contours = scene.points.iter().map(build_contour).collect::>(); + + Self { + config, + scene, + normalized, + contours, + } + } + + pub fn points_len(&self) -> usize { + self.scene.points.len() + } + + /// Get a scene that is normalized by the given point. + pub fn get_normalized_scene(&self, i: usize) -> &Scene { + &self.normalized[i] + } + + /// Given two points in the scene, return `a` is below `b`, + /// by checking if `b` is above the contour, + /// in the reference frame of `a`. + pub fn is_below(&self, a: usize, b: usize, contour: &Contour) -> bool { + self.product(a, b, contour) > 0f64 + } + + pub fn is_above(&self, a: usize, b: usize, contour: &Contour) -> bool { + self.product(a, b, contour) < 0f64 + } + + pub fn product(&self, unit: usize, p: usize, contour: &Contour) -> f64 { + let b_in_normalized_by_a = self.normalized[unit].points[p]; + contour.cross_product_x(&b_in_normalized_by_a) + } + + /// Count the number of conflicting point pairs. + /// + /// Return the total number of conflicts found for point `p`, using contour `contour`. + pub fn count_conflict(&self, p: usize, contour: &Contour) -> usize { + contour.validate(); + + let mut conflicts = 0; + for i in 0..self.points_len() { + if i == p { + continue; + } + + if self.is_below(p, i, contour) && self.is_below(i, p, &self.contours[i]) + || self.is_above(p, i, contour) && self.is_above(i, p, &self.contours[i]) + { + conflicts += 1; + } + } + conflicts + } + + /// Update the contour for the given point, find a better contour that has less conflicts with other points. + /// + /// Return the best contour found and the number of conflicts. + pub fn find_better_contour(&self, p: usize) -> (Contour, usize) { + let w = self.config.variant_weight; + let contour = self.contours[p].clone(); + let conflict = self.count_conflict(p, &contour); + + let mut best = (contour.clone(), conflict); + + for _i in 0..self.config.n_spawn { + let (new, _action) = contour.rand_update(w.0, w.1, w.2); + let conflict = self.count_conflict(p, &new); + + // Find the first better solution + if conflict < best.1 { + best = (new, conflict); + break; + } + } + + best + } + + pub fn evolve_one_round(&mut self) { + let mut next_generation = vec![]; + + for p in 0..self.contours.len() { + let (new_contour, _new_conflict) = self.find_better_contour(p); + next_generation.push(new_contour); + } + + next_generation.shuffle(&mut rand::thread_rng()); + + self.contours = next_generation; + } + + pub fn evolve(&mut self) { + // make dir `output`: + std::fs::create_dir_all("output/").unwrap(); + + let pref = "output/cc"; + + draw::draw_contour(format!("{pref}-0000.png"), &self.scene, &self.contours).unwrap(); + + for i in 1..=self.config.n_round { + println!("round {}", i); + self.evolve_one_round(); + + draw::draw_contour(format!("{pref}-{i:0>4}.png"), &self.scene, &self.contours).unwrap(); + } + } +} + +fn main() -> Result<(), Box> { + fn contour(points: impl IntoIterator) -> Contour { + Contour::new(points.into_iter().map(Point::from)) + } + + let config = Config { + n_points_per_scene: 1_000, + n_round: 100, + n_spawn: 10, + variant_weight: (5.0, 5.0, 5.0), + }; + let scene = Scene::rand_scene(10f64, 10f64, config.n_points_per_scene); + + let mut evolution = Evolution::new(config, scene, |_point| { + let x0 = rand::random::(); + let y0 = 1.0 + rand::random::(); + + let x2 = 1.0 + rand::random::(); + let y2 = rand::random::(); + contour([(x0, y0), (1.0, 1.0), (x2, y2)]) + }); + evolution.evolve(); + + Ok(()) +} diff --git a/src/list/raxos/evolution/src/point.rs b/src/list/raxos/evolution/src/point.rs new file mode 100644 index 0000000..737b026 --- /dev/null +++ b/src/list/raxos/evolution/src/point.rs @@ -0,0 +1,60 @@ +use std::fmt; +use std::ops::Sub; + +/// A point in the 2D space +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct Point { + pub x: f64, + pub y: f64, +} + +impl fmt::Display for Point { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, " {:.3}={:.3}x{:.3}", self.x * self.y, self.x, self.y) + } +} + +impl From<(f64, f64)> for Point { + fn from((x, y): (f64, f64)) -> Self { + Point { x, y } + } +} + +impl From<(u64, u64)> for Point { + fn from((x, y): (u64, u64)) -> Self { + Point { + x: x as f64, + y: y as f64, + } + } +} + +impl Sub for Point { + type Output = Point; + + fn sub(self, rhs: Self) -> Self::Output { + Point { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl Point { + pub fn new(x: f64, y: f64) -> Self { + Point { x, y } + } + + pub fn cross_product(&self, rhs: &Point) -> f64 { + self.x * rhs.y - self.y * rhs.x + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_display() { + let p = super::Point::new(1.12345, 2.678899); + assert_eq!(format!("{}", p), "(1.1235, 2.6789)"); + } +} diff --git a/src/list/raxos/evolution/src/scene.rs b/src/list/raxos/evolution/src/scene.rs new file mode 100644 index 0000000..36e04bc --- /dev/null +++ b/src/list/raxos/evolution/src/scene.rs @@ -0,0 +1,47 @@ +use crate::point::Point; + +pub struct Scene { + pub points: Vec, +} + +impl Scene { + fn new() -> Self { + Self { points: Vec::new() } + } + + /// Create a Scene with random points in specified range + pub fn rand_scene(x: f64, y: f64, n: usize) -> Scene { + let mut scene = Scene::new(); + for _ in 0..n { + let xx = rand::random::() * x; + let yy = rand::random::() * y; + scene.add_point(xx, yy); + } + scene + } + + fn add_point(&mut self, x: f64, y: f64) { + self.points.push(Point { x, y }); + } + + /// Create a new Scene by applying a non-uniform scaling transformation + /// that maps the given point to (1,1) and scales all other points accordingly. + /// + /// # Arguments + /// + /// * `reference_point` - The point that will be mapped to (1,1) + /// + /// # Returns + /// + /// A new Scene with all points transformed + pub fn normalize(&self, reference_point: Point) -> Self { + let points = self.points.iter().map(|point| Point { + x: point.x / reference_point.x, + y: point.y / reference_point.y, + }); + + Self { + points: points.collect(), + } + } +} diff --git a/src/list/raxos/history-committed-12.excalidraw.png b/src/list/raxos/history-committed-12.excalidraw.png new file mode 100644 index 0000000..d9f7de5 Binary files /dev/null and b/src/list/raxos/history-committed-12.excalidraw.png differ diff --git a/src/list/raxos/history-committed-123.excalidraw.png b/src/list/raxos/history-committed-123.excalidraw.png new file mode 100644 index 0000000..022fb3a Binary files /dev/null and b/src/list/raxos/history-committed-123.excalidraw.png differ diff --git a/src/list/raxos/history-data-loss.excalidraw.png b/src/list/raxos/history-data-loss.excalidraw.png new file mode 100644 index 0000000..1d7db8b Binary files /dev/null and b/src/list/raxos/history-data-loss.excalidraw.png differ diff --git a/src/list/raxos/history-dirty-write.excalidraw.png b/src/list/raxos/history-dirty-write.excalidraw.png new file mode 100644 index 0000000..6fbff37 Binary files /dev/null and b/src/list/raxos/history-dirty-write.excalidraw.png differ diff --git a/src/list/raxos/history-quorum.excalidraw.png b/src/list/raxos/history-quorum.excalidraw.png new file mode 100644 index 0000000..930f48d Binary files /dev/null and b/src/list/raxos/history-quorum.excalidraw.png differ diff --git a/src/list/raxos/history.excalidraw.png b/src/list/raxos/history.excalidraw.png new file mode 100644 index 0000000..b10206e Binary files /dev/null and b/src/list/raxos/history.excalidraw.png differ diff --git a/src/list/raxos/quorum-2x2.excalidraw.png b/src/list/raxos/quorum-2x2.excalidraw.png new file mode 100644 index 0000000..1e14796 Binary files /dev/null and b/src/list/raxos/quorum-2x2.excalidraw.png differ diff --git a/src/list/raxos/quorum-majority-3.excalidraw.png b/src/list/raxos/quorum-majority-3.excalidraw.png new file mode 100644 index 0000000..c729a35 Binary files /dev/null and b/src/list/raxos/quorum-majority-3.excalidraw.png differ diff --git a/src/list/raxos/quorum-nxn.excalidraw.png b/src/list/raxos/quorum-nxn.excalidraw.png new file mode 100644 index 0000000..5584789 Binary files /dev/null and b/src/list/raxos/quorum-nxn.excalidraw.png differ diff --git a/src/list/raxos/raxos.md b/src/list/raxos/raxos.md new file mode 100644 index 0000000..cd011a5 --- /dev/null +++ b/src/list/raxos/raxos.md @@ -0,0 +1,451 @@ +# 历史; 时间; 时间定序. + +一个分布式一致性协议中, 可以看做是对一个系统的状态达成一致的协议, +系统的状态(State), 可以等价的看做是一系列有序的, 对系统状态做出变更的**事件**(Event). +即任意一组有序事件唯一定义了一个系统的状态. +我们把这组**事件**称作系统事件的**历史**(Event History, 或History). + + + + +于是系统的State就可以用事件历史(DAG of events)来标识: +```rust +State: DAG; +``` + +其中Event表示State的变化, + + + +![](history.excalidraw.png) + +这与我们单机系统的状态定义也是一致的, 例如3个Event分别是: +E1: let x = 1; +E2: let y = 2; +E3: let x = x + y; +那么系统的State就是 State = apply(History) = apply(E1, E2, E3) = { x = 3; y = 2; } + +现在我们有了一个系统State的描述方式, 接下来在分布式环境中把它实现为高可用的分布式State, +也就是通过多个Event History的副本来实现高可用. + + + + + + + + +在分布式系统中, 系统状态定义为每个节点上存储的History的副本的集合: + +```rust +State: BTreeMap +``` + +## History Quorum + +在分布式环境中, 一个读操作可以看做一个函数: `fn read(node_set: Vec) -> Vec`. +它从多个节点中读History副本, 并返回一个History的集合. + +对读到的History中的任意一个, 我们可以称这个`node_set` 是这个`Hisotry`的一个quorum. 表示这个`History` 可以通过这个`node_set`(quorum)读到. + +对系统的某个特定的状态, +Histroy的quorum定义为可以读到这个History的一个节点的集合. + +例如, 在下面这个3节点的系统中, `History{E1,E2,E3}`的quorum是所有包括N1节点的节点集合: + +`{N1}, {N1,N2}, {N1,N2,N3}, {N1,N3}` + +它有4个可以读到它的quorum. + +例如`read({N1})` 会返回`History{E1,E2,E3}`在结果里, +`read({N1,N3})` 也会返回`History{E1,E2,E3}`在结果里. + +但是`read({N3})` 不会返回`History{E1,E2,E3}`. + +而`History{E1,E2}`的quorum有5个, 除了`{N3}` 之外的所有非空节点集合都是它的quorum: +`{N1}, {N1,N2}, {N1,N2,N3}, {N1,N3}, {N2,N3}`, + +例如`read({N2,N3})` 会返回`History{E1,E2}`在结果里. + + +![](history-quorum.excalidraw.png) + + +## Node set for reading + +每个系统都定义了`read()` 操作可用的node_set 有哪些: + +- 例如单机系统, `read()` 可用的node_set就是唯一这个节点`{{N1}}`, + 显然用一个空的`node_set`去读是不允许的. + +- 一个简单3节点系统中, 不做任何限制, + 那么`read()`可用的`node_set`是所有非空节点集合: `{{N1}, {N2}, {N3}, {N1,N2}, {N2,N3}, {N1,N3}, {N1,N2,N3}}` + 但注意这样一个系统中`read()`操作的结果是没有任何保证的. + +- 一个多数派读写的3节点系统中, + `read()`可用的`node_set`是至少包含2节点的集合: `{{N1,N2}, {N2,N3}, {N1,N3}, {N1,N2,N3}}`, + +如果一个read操作使用的`node_set`是这个系统定义的可用于读的`node_set`, +那么认为这个read操作是合法的, 对于不合法的read操作, +系统对读取的结果不能提供任何保证. + +这个合法的`node_set`的集合, 就是系统的`read_quorum_set`, `read_quorum_set` +中的一个元素称之为一个`read_quorum`. + +例如 3节点的系统 的`read_quorum_set`是: `{{N1,N2}, {N2,N3}, {N1,N3}, {N1,N2,N3}}`, +那么`read({N1})`, 系统就不能提供任何保证. + + +### Write quorum set + +如果一个`node_set`的集合, +其中每个node_set都跟给定的`read_quorum_set`中的一个`node_set`有交集, +那么就称它是一个合法的`write_quorum_set`, +即保证每次写入都能被读到. + + +## 单个 History的Commit状态 + +对一个History, 如果它的quorum集合覆盖了所有的 `read_quorum`, +那么它就总是能被一次合法的读操作读到, +那么这个History就可以认为是在系统的某个状态时, 是Committed的. + +### Commit 的定义 + +这里Commit的定义也很直观, 对一个History, 如果 +- 1 读操作可以通过系统中`read_quorum_set`中任意一个quorum读到它, + +这里还只是对某个系统的状态的Commit, + + + +注意这跟系统的状态State是有关的, 这个系统可能发生变化导致History不能被读到了, +那么这时就认为发生了数据丢失. +例如, 一个3节点系统中, `read_quorum_set` 定义为 +`{{N1,N2}, {N2,N3}, {N1,N3}}`, +这时如果 E3 从 N1 节点上删除了, 在这个系统状态的变化中, 就导致了`History{N1, N2, N3}`的丢失, +因为 `read_quorum` `{N1, N2}` 无法读到 `History{N1, N2, N3}`. + +但是`History{N1, N2}` 没有发生数据丢失, 因为从任意一个`read_quorum` +都可以读到它. + +![](history-data-loss.excalidraw.png) + + +## 高可用的定义 + +可以看出, Commit的是跟`read_quorum_set`相关的, `read_quorum_set`越大, +那么Committed达成的条件就越苛刻. 例如不做限制的3节点系统, +达成Committed要求从任意节点都能读到这个History. + +高可用的定义(对读的定义)就是只需要一个`node_set`集合的子集就可以读到这个History. +或者说, 这个`read()`操作容忍了某些个`node_set`子集的的故障. + + + + +## 完整的 Committed 的定义 + +如果一个History从系统某个状态开始, +后面所有的系统状态(包括每个节点上History副本变化和read_quorum_set变化), +都能通过`read_quorum_set`读到, 那么这个History就是 Committed. + + +## 多重宇宙里的分布式一致性 + +如果允许History非线性(到目前为止我们都没有要求History是线性的), +那么以上就是分布式高可用一致性算法的实现: + +向一个`write_quorum` 添加(不是替换)一个History 分支, +那么一定可以被一个`read_quorum` 读到, 那么这次写入就可以认为是Committed. + +例如: 加图 + + +因为History分支之间没有关联, 所以一个write操作可以读任意一个History, +加入新的Event构造一个新的History, 再将其写入到一个`write_quorum`, 即完成Commit. +当然新加入的Event可以依赖于已有的多个Event. 这样History会形成一个 Event的DAG. +否则History是一个树. +这里我们假设一个Event不会被多次加入到History里, 所以这个图是无环的. + + +可以看出, 我们现在没有引入时间的概念, +如果允许多重历史的存在, 那么就不存在时间的概念. + +但是现在我们如果要求历史必须是线性的(即我们不能同时存在于多于一个历史时间线), +这也是我们常识认知中的现实. +那么就会引入时间的概念. + + + +## 限制单一历史 + +但是我们现在希望得到一个线性History, +即至多只有一个无分支的History是Committed. + +`read()` 现在返回一个DAG, 它是所有节点上读到的History的并集, +它可能有多个末端端点, `read()` 函数必须只能选择其中一个作为读到的History的结果. +而且对于一个Committed的History, 多个`read()` 总是选择它. +例如(加图) 分支A和分支B同时被读到, 那么就总是选择A +这说明每个History分支之间有一个二元关系, 也就是一个全序关系. +Committed的History分支, 必须是最大的. + +表示这个全序关系, 就说明每个History分支都有一个全序关系的属性 T, +也就是说History上每个Event都对应一个全序关系的属性 T. + +每次Commit一个Event, 它必须具有全局中最大的T. + +或者说, 随着History中Event的增加, T是单调递增的, 永远不会回退. + +所以我们称之为这个分布式系统的**虚拟的** 时间. + +所以说, 时间可以理解为将高纬度的空间映射到一维上的一个映射关系. + + + + + + + +## Commit 在线性History约束中的定义 + +这里Commit的定义也很直观, 对一个History, 如果 +- 1 读操作可以通过系统中`read_quorum_set`中任意一个quorum读到它, +- 2 且总是能读到, + +它就是Committed. + +第二个条件是显然要保证的, 否则认为数据丢失了. + +例如下面的这个副本状态中: +- 如果定义系统的`read_quorum_set`是多数派,即`{{N1,N2}, {N1,N3},{N2,N3}}`, 那么`History{E1,E2}` 是Committed状态, 总是能被读到, `History{E1,E2,E3}` 不是,因为`read({N2,N3})` 不会返回它. + + +![](history-committted-12.excalidraw.png) + +- 而如果更改系统的`read_quorum_set`定义, 改成`{{N1,N2}, {N1,N3}}`, 即要求所有读操作都必须读到N1节点,那么`History{E1,E2,E3}` 就可以认为是Committed状态. + +![](history-committted-123.excalidraw.png) + + + +# Write约束 + +## Write只追加History + +这表示writer 写入时, 不能覆盖已有History, 只能追加. +即如果一个节点上的History是`{E1,E2}`, 那么不能被替换成`{E1,E3}`, 可以替换成`{E1,E2,E3}`. + + + +## Write Prepare + +虽然Write写到了`write_quorum_set`, 但是2因为read 会选择最大History, 所以可能产生一个问题, 因为有更大的History, 导致read忽略了写到`write_quorum_set`的History: + +例如可能有2个Writer W3 和 W4, W3写了N1,N2, W4写了N3. +我们假设系统的`read_quorum_set`是多数派模式, 那么W3写的History{E1,E2,E3} 虽然能被任意一个read读到, +但是如果read操作选择了quorum {N2,N3}, 那么它会选择W4写入的更大的History{E1,E2,E4}, +如果read操作选择了quorum {N1,N2}, 那么它会选择W3写入的更大的History{E1,E2,E3}, +这违背了Commit的原则, 没有达到**总是能被读到**的要求. + +![](history-dirty-write.excalidraw.png) + +## Write阻止更小的Hisotry被Commit + +所以Writer把History写到节点上前, 必须要求没有更小的History被Commit. +所以假设Write要写的History的Time是T, 首先要发一个消息, 给一个`read_quorum`, 要求这个`read_quorum`里的节点都不接受小于T的write消息. +之所以是要写到一个`read_quorum`, 是因为系统中任意一个read_quorum跟任意一个write_quorum有交集, 但是write_quorum之间, 或read_quorum之间没有必须有交集的约束. +所以为了阻止其他write, 要写到一个read_quorum里. + +## Write 要基于已经Commit的History追加 + +阻止了更小History的写入后, writer就可以选择一个在T时间的History来写入(即History的最后一个Event的Time是T), +但是写入的History仍然不能破坏Commit的约束, 不能覆盖已经可能Commit的History. +所以writer还要联系一个`read_quorum`, 进行一次读操作, +因为read操作保证读到Commit的History, 所以Writer在这个读到的History上追加新的Event再写入(paxos), 就不会造成已Commit的History丢失. +如果不满足这个条件, 就不能继续(raft). + +这次读操作, 可以选择不同的`read_quorum`; +如果看到更大的History, 那表示无法写入, 要终止. + +## 执行写入! + +执行具体的write过程相对简单, 直接将整个History 复制到一个`write_quorum`, 完成Commit. + + + +为了简化设计, 我们假设每个writer新增 + +TODO: 图: + +所以, 最终我们得到的分布式一致性算法为: + +每个Node存储的数据为: 一个线性的History路径, 和一个最小可Commit的时间 +```rust +struct Node { + least_commit_time: Time, + history: BTreeMap, +} +``` + +Phase-1 + +Phase-2 + + +# Classic Paxos + +将其简化成 Paxos 是非常直接的, 只需限制history不能包含多于1个Event; + +# Raft + +将其转变成 Raft 也很简单, +将Event设计成一个sub-event的容器,其中每个sub-event也分配一个时间`(T, index)`. +并要求在读Committed的阶段也考虑这个sub-Time. + +剩下的就是将整块的History的复制实现为分段传输的协议就好了. + +是不是很简单? + + +# 非线性一致性协议 + +以上我们将这个算法简化到了线性History, +实际上本文的目标是考虑非线性History会带来什么样的东西. + +假如我们抛弃简陋的Paxos 和 Raft的限制, 允许Time是偏序的关系(或DAG关系这里?), +上面的协议会变成: + +Phase-1 中, 仍然是小于 least 的Time不允许Commit, +Phase-1读中, 我们读到的最大History分支, 变成了读一个DAG History的 maximum(fix this term). +Phase-2 append Event 时, 它依赖于所有Time小于它的Event + +现在我们可以证明这个算法允许同时Commit多个Event并行进行, +且也能满足正确性: + +- Commit 后的值不会丢失, 总是能读到 + + +# 选择二维向量Time + +那么我们可以选择任何偏序的Time类型作为系统的时间. +我们现在选择二维向量, 即 `Time: (x i + y j)`. +其中i, j是2个维度, x, y表示Time在这两个维度上的值. +注意x和y可能是完全不同的类型, + +## 二维之间的大小关系 + +`Time(xi+yj)` 中, 显然 `x₁ >= x₂ && y₁ >= y₂` 则 `T₁ >= T₂`, +但`x₁ > x₂ && y₁ < y₂`的情况, 在没有其他条件下是不能比较大小的. + +## 二维Time的工作流程 + +例如, 现在系统的状态是有一个E1, +E2 和 E3 的 Proposer 同时开始执行, 同时commit了E2和E3, +E4 只能看到E2, 也Commit了, +E5看到了所有, 最后Commit. + + +## 二维Time的应用 + +例如我们可以用这种方法对Raft进行一个扩展, +i就是raft中的term, 保持不变. +j是墙上时钟的unix-timestamp, +然后将`Time: (x i + y j)` 替换Raft中的term, 来构建一个分布式系统. +这时我们会得到一个改进版的Raft: 在每个成员有时钟飘逸的情况下, +新的Leader也总是有更大的unix-timestamp, +这个分布式系统可以用来构建分布式授时服务, 优雅的解决Leader切换时的时间可能回退问题. + +我举这个例子是为了说明, Time的每个维度之间的类型不能假设是一样的, +即不支持加法, 但可以支持乘法. 这将会引出后面的有趣的结论. + + +## Apply 阶段 + +分布式一致性算法的核心一个是log的提交(对应我们的Event History), +另一个是状态机的实现, 也就是将log(History) 以业务定义的方式进行解释. + +我们上面提到, 系统的状态由History构成, 细心的你现在一定发现了一个问题, +就是对一个例如二维Time的History来说, apply 这些 Event的顺序是不确定的. + + +如果我们有量子计算机, +那么一个可行的实现是将所有符合DAG顺序的顺序来apply一次History, +这样所有的可能并存. + +但是我们没有. + + +所以每个副本节点上, 我们仍然需要一个相互间一致的顺序来apply已commit的History. + +而对于二维Time来说, 就是怎么为`x₁ > x₂ && y₁ < y₂` 关系的Time确定一个顺序. + + +假设找到2个T1, 和 T2是相等的关系, + +相对T1来说, T2是`x2/x1, y2/y1` + + +commit的定义: 总是能读到(因为: 可能脏读: 最大历史不在quorum里) +增加新历史前必须阻止其他写(阻止所有: 死锁, 2pc; 阻止旧的: raft, paxos)(为了满足commit) +自己不是最大历史不能写: 因为增加新历史前不能覆盖已有的(为了满足commit) +(一个简化的raft: 可以vote for非最大日志的candidate, 但是candidate自己不能继续写) +(paxos的不同做法是: 把最大历史拿过来) + +raft 接append-entries时不需要用(t2, a) 覆盖 (t2,b)