├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── benches.rs ├── examples ├── chameleon ├── iguana └── leopard ├── rustfmt.toml ├── src ├── lib.rs └── main.rs └── xtask ├── Cargo.toml └── src └── xtask.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | rust: 15 | - stable 16 | - beta 17 | steps: 18 | - uses: actions/checkout@v3 19 | name: "Checkout source" 20 | - uses: actions-rs/toolchain@v1.0.7 21 | name: "Install Rust" 22 | with: 23 | profile: default 24 | toolchain: ${{ matrix.rust }} 25 | override: true 26 | - uses: Swatinem/rust-cache@v2 27 | name: "Cache dependencies" 28 | - uses: actions-rs/cargo@v1.0.3 29 | name: "Build, test, and check" 30 | with: 31 | command: xtask 32 | args: ci 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anes" 22 | version = "0.1.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.68" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 31 | 32 | [[package]] 33 | name = "approx" 34 | version = "0.5.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 37 | dependencies = [ 38 | "num-traits", 39 | ] 40 | 41 | [[package]] 42 | name = "atty" 43 | version = "0.2.14" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 46 | dependencies = [ 47 | "hermit-abi 0.1.19", 48 | "libc", 49 | "winapi", 50 | ] 51 | 52 | [[package]] 53 | name = "autocfg" 54 | version = "1.1.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 57 | 58 | [[package]] 59 | name = "backtrace" 60 | version = "0.3.65" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" 63 | dependencies = [ 64 | "addr2line", 65 | "cc", 66 | "cfg-if", 67 | "libc", 68 | "miniz_oxide", 69 | "object", 70 | "rustc-demangle", 71 | ] 72 | 73 | [[package]] 74 | name = "bitflags" 75 | version = "1.3.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 78 | 79 | [[package]] 80 | name = "bumpalo" 81 | version = "3.10.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 84 | 85 | [[package]] 86 | name = "bytemuck" 87 | version = "1.12.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" 90 | 91 | [[package]] 92 | name = "cast" 93 | version = "0.3.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 96 | 97 | [[package]] 98 | name = "cc" 99 | version = "1.0.73" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 102 | 103 | [[package]] 104 | name = "cfg-if" 105 | version = "1.0.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 108 | 109 | [[package]] 110 | name = "ciborium" 111 | version = "0.2.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" 114 | dependencies = [ 115 | "ciborium-io", 116 | "ciborium-ll", 117 | "serde", 118 | ] 119 | 120 | [[package]] 121 | name = "ciborium-io" 122 | version = "0.2.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" 125 | 126 | [[package]] 127 | name = "ciborium-ll" 128 | version = "0.2.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" 131 | dependencies = [ 132 | "ciborium-io", 133 | "half", 134 | ] 135 | 136 | [[package]] 137 | name = "clap" 138 | version = "3.2.22" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" 141 | dependencies = [ 142 | "bitflags", 143 | "clap_lex 0.2.2", 144 | "indexmap", 145 | "textwrap", 146 | ] 147 | 148 | [[package]] 149 | name = "clap" 150 | version = "4.1.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" 153 | dependencies = [ 154 | "bitflags", 155 | "clap_derive", 156 | "clap_lex 0.3.0", 157 | "is-terminal", 158 | "once_cell", 159 | "strsim", 160 | "termcolor", 161 | ] 162 | 163 | [[package]] 164 | name = "clap_derive" 165 | version = "4.1.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 168 | dependencies = [ 169 | "heck", 170 | "proc-macro-error", 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "clap_lex" 178 | version = "0.2.2" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" 181 | dependencies = [ 182 | "os_str_bytes", 183 | ] 184 | 185 | [[package]] 186 | name = "clap_lex" 187 | version = "0.3.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 190 | dependencies = [ 191 | "os_str_bytes", 192 | ] 193 | 194 | [[package]] 195 | name = "criterion" 196 | version = "0.4.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 199 | dependencies = [ 200 | "anes", 201 | "atty", 202 | "cast", 203 | "ciborium", 204 | "clap 3.2.22", 205 | "criterion-plot", 206 | "itertools", 207 | "lazy_static", 208 | "num-traits", 209 | "oorandom", 210 | "plotters", 211 | "rayon", 212 | "regex", 213 | "serde", 214 | "serde_derive", 215 | "serde_json", 216 | "tinytemplate", 217 | "walkdir", 218 | ] 219 | 220 | [[package]] 221 | name = "criterion-plot" 222 | version = "0.5.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 225 | dependencies = [ 226 | "cast", 227 | "itertools", 228 | ] 229 | 230 | [[package]] 231 | name = "crossbeam-channel" 232 | version = "0.5.4" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" 235 | dependencies = [ 236 | "cfg-if", 237 | "crossbeam-utils", 238 | ] 239 | 240 | [[package]] 241 | name = "crossbeam-deque" 242 | version = "0.8.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 245 | dependencies = [ 246 | "cfg-if", 247 | "crossbeam-epoch", 248 | "crossbeam-utils", 249 | ] 250 | 251 | [[package]] 252 | name = "crossbeam-epoch" 253 | version = "0.9.8" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" 256 | dependencies = [ 257 | "autocfg", 258 | "cfg-if", 259 | "crossbeam-utils", 260 | "lazy_static", 261 | "memoffset", 262 | "scopeguard", 263 | ] 264 | 265 | [[package]] 266 | name = "crossbeam-utils" 267 | version = "0.8.8" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" 270 | dependencies = [ 271 | "cfg-if", 272 | "lazy_static", 273 | ] 274 | 275 | [[package]] 276 | name = "either" 277 | version = "1.6.1" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 280 | 281 | [[package]] 282 | name = "errno" 283 | version = "0.2.8" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 286 | dependencies = [ 287 | "errno-dragonfly", 288 | "libc", 289 | "winapi", 290 | ] 291 | 292 | [[package]] 293 | name = "errno-dragonfly" 294 | version = "0.1.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 297 | dependencies = [ 298 | "cc", 299 | "libc", 300 | ] 301 | 302 | [[package]] 303 | name = "failure" 304 | version = "0.1.8" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 307 | dependencies = [ 308 | "backtrace", 309 | "failure_derive", 310 | ] 311 | 312 | [[package]] 313 | name = "failure_derive" 314 | version = "0.1.8" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn", 321 | "synstructure", 322 | ] 323 | 324 | [[package]] 325 | name = "getrandom" 326 | version = "0.2.6" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 329 | dependencies = [ 330 | "cfg-if", 331 | "libc", 332 | "wasi", 333 | ] 334 | 335 | [[package]] 336 | name = "gimli" 337 | version = "0.26.1" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 340 | 341 | [[package]] 342 | name = "half" 343 | version = "1.8.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 346 | 347 | [[package]] 348 | name = "hashbrown" 349 | version = "0.11.2" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 352 | 353 | [[package]] 354 | name = "heck" 355 | version = "0.4.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 358 | 359 | [[package]] 360 | name = "hermit-abi" 361 | version = "0.1.19" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 364 | dependencies = [ 365 | "libc", 366 | ] 367 | 368 | [[package]] 369 | name = "hermit-abi" 370 | version = "0.2.6" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 373 | dependencies = [ 374 | "libc", 375 | ] 376 | 377 | [[package]] 378 | name = "indexmap" 379 | version = "1.8.2" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" 382 | dependencies = [ 383 | "autocfg", 384 | "hashbrown", 385 | ] 386 | 387 | [[package]] 388 | name = "io-lifetimes" 389 | version = "1.0.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" 392 | dependencies = [ 393 | "libc", 394 | "windows-sys", 395 | ] 396 | 397 | [[package]] 398 | name = "is-terminal" 399 | version = "0.4.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" 402 | dependencies = [ 403 | "hermit-abi 0.2.6", 404 | "io-lifetimes", 405 | "rustix", 406 | "windows-sys", 407 | ] 408 | 409 | [[package]] 410 | name = "itertools" 411 | version = "0.10.3" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 414 | dependencies = [ 415 | "either", 416 | ] 417 | 418 | [[package]] 419 | name = "itoa" 420 | version = "1.0.2" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 423 | 424 | [[package]] 425 | name = "js-sys" 426 | version = "0.3.57" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" 429 | dependencies = [ 430 | "wasm-bindgen", 431 | ] 432 | 433 | [[package]] 434 | name = "lazy_static" 435 | version = "1.4.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 438 | 439 | [[package]] 440 | name = "libc" 441 | version = "0.2.137" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 444 | 445 | [[package]] 446 | name = "libm" 447 | version = "0.2.2" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" 450 | 451 | [[package]] 452 | name = "linux-raw-sys" 453 | version = "0.1.3" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" 456 | 457 | [[package]] 458 | name = "log" 459 | version = "0.4.17" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 462 | dependencies = [ 463 | "cfg-if", 464 | ] 465 | 466 | [[package]] 467 | name = "matrixmultiply" 468 | version = "0.3.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" 471 | dependencies = [ 472 | "rawpointer", 473 | ] 474 | 475 | [[package]] 476 | name = "memchr" 477 | version = "2.5.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 480 | 481 | [[package]] 482 | name = "memoffset" 483 | version = "0.6.5" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 486 | dependencies = [ 487 | "autocfg", 488 | ] 489 | 490 | [[package]] 491 | name = "miniz_oxide" 492 | version = "0.5.3" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 495 | dependencies = [ 496 | "adler", 497 | ] 498 | 499 | [[package]] 500 | name = "nalgebra" 501 | version = "0.29.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" 504 | dependencies = [ 505 | "approx", 506 | "matrixmultiply", 507 | "nalgebra-macros", 508 | "num-complex", 509 | "num-rational", 510 | "num-traits", 511 | "rand", 512 | "rand_distr", 513 | "simba", 514 | "typenum", 515 | ] 516 | 517 | [[package]] 518 | name = "nalgebra-macros" 519 | version = "0.1.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" 522 | dependencies = [ 523 | "proc-macro2", 524 | "quote", 525 | "syn", 526 | ] 527 | 528 | [[package]] 529 | name = "nanostat" 530 | version = "0.2.1-alpha.0" 531 | dependencies = [ 532 | "approx", 533 | "clap 4.1.1", 534 | "criterion", 535 | "plotlib", 536 | "statrs", 537 | ] 538 | 539 | [[package]] 540 | name = "num-complex" 541 | version = "0.4.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" 544 | dependencies = [ 545 | "num-traits", 546 | ] 547 | 548 | [[package]] 549 | name = "num-integer" 550 | version = "0.1.45" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 553 | dependencies = [ 554 | "autocfg", 555 | "num-traits", 556 | ] 557 | 558 | [[package]] 559 | name = "num-rational" 560 | version = "0.4.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" 563 | dependencies = [ 564 | "autocfg", 565 | "num-integer", 566 | "num-traits", 567 | ] 568 | 569 | [[package]] 570 | name = "num-traits" 571 | version = "0.2.15" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 574 | dependencies = [ 575 | "autocfg", 576 | "libm", 577 | ] 578 | 579 | [[package]] 580 | name = "num_cpus" 581 | version = "1.13.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 584 | dependencies = [ 585 | "hermit-abi 0.1.19", 586 | "libc", 587 | ] 588 | 589 | [[package]] 590 | name = "object" 591 | version = "0.28.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" 594 | dependencies = [ 595 | "memchr", 596 | ] 597 | 598 | [[package]] 599 | name = "once_cell" 600 | version = "1.12.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 603 | 604 | [[package]] 605 | name = "oorandom" 606 | version = "11.1.3" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 609 | 610 | [[package]] 611 | name = "os_str_bytes" 612 | version = "6.1.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 615 | 616 | [[package]] 617 | name = "paste" 618 | version = "1.0.7" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" 621 | 622 | [[package]] 623 | name = "plotlib" 624 | version = "0.5.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "9462104f987d8d0f6625f0c7764f1c8b890bd1dc8584d8293e031f25c5a0d242" 627 | dependencies = [ 628 | "failure", 629 | "svg", 630 | ] 631 | 632 | [[package]] 633 | name = "plotters" 634 | version = "0.3.1" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" 637 | dependencies = [ 638 | "num-traits", 639 | "plotters-backend", 640 | "plotters-svg", 641 | "wasm-bindgen", 642 | "web-sys", 643 | ] 644 | 645 | [[package]] 646 | name = "plotters-backend" 647 | version = "0.3.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" 650 | 651 | [[package]] 652 | name = "plotters-svg" 653 | version = "0.3.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" 656 | dependencies = [ 657 | "plotters-backend", 658 | ] 659 | 660 | [[package]] 661 | name = "ppv-lite86" 662 | version = "0.2.16" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 665 | 666 | [[package]] 667 | name = "proc-macro-error" 668 | version = "1.0.4" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 671 | dependencies = [ 672 | "proc-macro-error-attr", 673 | "proc-macro2", 674 | "quote", 675 | "syn", 676 | "version_check", 677 | ] 678 | 679 | [[package]] 680 | name = "proc-macro-error-attr" 681 | version = "1.0.4" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 684 | dependencies = [ 685 | "proc-macro2", 686 | "quote", 687 | "version_check", 688 | ] 689 | 690 | [[package]] 691 | name = "proc-macro2" 692 | version = "1.0.46" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" 695 | dependencies = [ 696 | "unicode-ident", 697 | ] 698 | 699 | [[package]] 700 | name = "quote" 701 | version = "1.0.18" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 704 | dependencies = [ 705 | "proc-macro2", 706 | ] 707 | 708 | [[package]] 709 | name = "rand" 710 | version = "0.8.5" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 713 | dependencies = [ 714 | "libc", 715 | "rand_chacha", 716 | "rand_core", 717 | ] 718 | 719 | [[package]] 720 | name = "rand_chacha" 721 | version = "0.3.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 724 | dependencies = [ 725 | "ppv-lite86", 726 | "rand_core", 727 | ] 728 | 729 | [[package]] 730 | name = "rand_core" 731 | version = "0.6.3" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 734 | dependencies = [ 735 | "getrandom", 736 | ] 737 | 738 | [[package]] 739 | name = "rand_distr" 740 | version = "0.4.3" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" 743 | dependencies = [ 744 | "num-traits", 745 | "rand", 746 | ] 747 | 748 | [[package]] 749 | name = "rawpointer" 750 | version = "0.2.1" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 753 | 754 | [[package]] 755 | name = "rayon" 756 | version = "1.5.3" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" 759 | dependencies = [ 760 | "autocfg", 761 | "crossbeam-deque", 762 | "either", 763 | "rayon-core", 764 | ] 765 | 766 | [[package]] 767 | name = "rayon-core" 768 | version = "1.9.3" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" 771 | dependencies = [ 772 | "crossbeam-channel", 773 | "crossbeam-deque", 774 | "crossbeam-utils", 775 | "num_cpus", 776 | ] 777 | 778 | [[package]] 779 | name = "regex" 780 | version = "1.5.6" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 783 | dependencies = [ 784 | "regex-syntax", 785 | ] 786 | 787 | [[package]] 788 | name = "regex-syntax" 789 | version = "0.6.26" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 792 | 793 | [[package]] 794 | name = "rustc-demangle" 795 | version = "0.1.21" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 798 | 799 | [[package]] 800 | name = "rustix" 801 | version = "0.36.4" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" 804 | dependencies = [ 805 | "bitflags", 806 | "errno", 807 | "io-lifetimes", 808 | "libc", 809 | "linux-raw-sys", 810 | "windows-sys", 811 | ] 812 | 813 | [[package]] 814 | name = "ryu" 815 | version = "1.0.10" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 818 | 819 | [[package]] 820 | name = "safe_arch" 821 | version = "0.6.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" 824 | dependencies = [ 825 | "bytemuck", 826 | ] 827 | 828 | [[package]] 829 | name = "same-file" 830 | version = "1.0.6" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 833 | dependencies = [ 834 | "winapi-util", 835 | ] 836 | 837 | [[package]] 838 | name = "scopeguard" 839 | version = "1.1.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 842 | 843 | [[package]] 844 | name = "serde" 845 | version = "1.0.137" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 848 | dependencies = [ 849 | "serde_derive", 850 | ] 851 | 852 | [[package]] 853 | name = "serde_derive" 854 | version = "1.0.137" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 857 | dependencies = [ 858 | "proc-macro2", 859 | "quote", 860 | "syn", 861 | ] 862 | 863 | [[package]] 864 | name = "serde_json" 865 | version = "1.0.81" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 868 | dependencies = [ 869 | "itoa", 870 | "ryu", 871 | "serde", 872 | ] 873 | 874 | [[package]] 875 | name = "simba" 876 | version = "0.6.0" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" 879 | dependencies = [ 880 | "approx", 881 | "num-complex", 882 | "num-traits", 883 | "paste", 884 | "wide", 885 | ] 886 | 887 | [[package]] 888 | name = "statrs" 889 | version = "0.16.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "2d08e5e1748192713cc281da8b16924fb46be7b0c2431854eadc785823e5696e" 892 | dependencies = [ 893 | "approx", 894 | "lazy_static", 895 | "nalgebra", 896 | "num-traits", 897 | "rand", 898 | ] 899 | 900 | [[package]] 901 | name = "strsim" 902 | version = "0.10.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 905 | 906 | [[package]] 907 | name = "svg" 908 | version = "0.7.2" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "3685c82a045a6af0c488f0550b0f52b4c77d2a52b0ca8aba719f9d268fa96965" 911 | 912 | [[package]] 913 | name = "syn" 914 | version = "1.0.96" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" 917 | dependencies = [ 918 | "proc-macro2", 919 | "quote", 920 | "unicode-ident", 921 | ] 922 | 923 | [[package]] 924 | name = "synstructure" 925 | version = "0.12.6" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 928 | dependencies = [ 929 | "proc-macro2", 930 | "quote", 931 | "syn", 932 | "unicode-xid", 933 | ] 934 | 935 | [[package]] 936 | name = "termcolor" 937 | version = "1.1.3" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 940 | dependencies = [ 941 | "winapi-util", 942 | ] 943 | 944 | [[package]] 945 | name = "textwrap" 946 | version = "0.15.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" 949 | 950 | [[package]] 951 | name = "tinytemplate" 952 | version = "1.2.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 955 | dependencies = [ 956 | "serde", 957 | "serde_json", 958 | ] 959 | 960 | [[package]] 961 | name = "typenum" 962 | version = "1.15.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 965 | 966 | [[package]] 967 | name = "unicode-ident" 968 | version = "1.0.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 971 | 972 | [[package]] 973 | name = "unicode-xid" 974 | version = "0.2.3" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 977 | 978 | [[package]] 979 | name = "version_check" 980 | version = "0.9.4" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 983 | 984 | [[package]] 985 | name = "walkdir" 986 | version = "2.3.2" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 989 | dependencies = [ 990 | "same-file", 991 | "winapi", 992 | "winapi-util", 993 | ] 994 | 995 | [[package]] 996 | name = "wasi" 997 | version = "0.10.2+wasi-snapshot-preview1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1000 | 1001 | [[package]] 1002 | name = "wasm-bindgen" 1003 | version = "0.2.80" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 1006 | dependencies = [ 1007 | "cfg-if", 1008 | "wasm-bindgen-macro", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "wasm-bindgen-backend" 1013 | version = "0.2.80" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 1016 | dependencies = [ 1017 | "bumpalo", 1018 | "lazy_static", 1019 | "log", 1020 | "proc-macro2", 1021 | "quote", 1022 | "syn", 1023 | "wasm-bindgen-shared", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "wasm-bindgen-macro" 1028 | version = "0.2.80" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 1031 | dependencies = [ 1032 | "quote", 1033 | "wasm-bindgen-macro-support", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "wasm-bindgen-macro-support" 1038 | version = "0.2.80" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 1041 | dependencies = [ 1042 | "proc-macro2", 1043 | "quote", 1044 | "syn", 1045 | "wasm-bindgen-backend", 1046 | "wasm-bindgen-shared", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "wasm-bindgen-shared" 1051 | version = "0.2.80" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 1054 | 1055 | [[package]] 1056 | name = "web-sys" 1057 | version = "0.3.57" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" 1060 | dependencies = [ 1061 | "js-sys", 1062 | "wasm-bindgen", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "wide" 1067 | version = "0.7.4" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "b3aba2d1dac31ac7cae82847ac5b8be822aee8f99a4e100f279605016b185c5f" 1070 | dependencies = [ 1071 | "bytemuck", 1072 | "safe_arch", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "winapi" 1077 | version = "0.3.9" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1080 | dependencies = [ 1081 | "winapi-i686-pc-windows-gnu", 1082 | "winapi-x86_64-pc-windows-gnu", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "winapi-i686-pc-windows-gnu" 1087 | version = "0.4.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1090 | 1091 | [[package]] 1092 | name = "winapi-util" 1093 | version = "0.1.5" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1096 | dependencies = [ 1097 | "winapi", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "winapi-x86_64-pc-windows-gnu" 1102 | version = "0.4.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1105 | 1106 | [[package]] 1107 | name = "windows-sys" 1108 | version = "0.42.0" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1111 | dependencies = [ 1112 | "windows_aarch64_gnullvm", 1113 | "windows_aarch64_msvc", 1114 | "windows_i686_gnu", 1115 | "windows_i686_msvc", 1116 | "windows_x86_64_gnu", 1117 | "windows_x86_64_gnullvm", 1118 | "windows_x86_64_msvc", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "windows_aarch64_gnullvm" 1123 | version = "0.42.0" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 1126 | 1127 | [[package]] 1128 | name = "windows_aarch64_msvc" 1129 | version = "0.42.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 1132 | 1133 | [[package]] 1134 | name = "windows_i686_gnu" 1135 | version = "0.42.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 1138 | 1139 | [[package]] 1140 | name = "windows_i686_msvc" 1141 | version = "0.42.0" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 1144 | 1145 | [[package]] 1146 | name = "windows_x86_64_gnu" 1147 | version = "0.42.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 1150 | 1151 | [[package]] 1152 | name = "windows_x86_64_gnullvm" 1153 | version = "0.42.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 1156 | 1157 | [[package]] 1158 | name = "windows_x86_64_msvc" 1159 | version = "0.42.0" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 1162 | 1163 | [[package]] 1164 | name = "xshell" 1165 | version = "0.2.3" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" 1168 | dependencies = [ 1169 | "xshell-macros", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "xshell-macros" 1174 | version = "0.2.3" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" 1177 | 1178 | [[package]] 1179 | name = "xtask" 1180 | version = "0.1.0" 1181 | dependencies = [ 1182 | "anyhow", 1183 | "clap 4.1.1", 1184 | "xshell", 1185 | ] 1186 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nanostat" 3 | version = "0.2.1-alpha.0" 4 | authors = ["Coda Hale "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "nanostat compares data sets using Welch's t-test at various levels of confidence." 8 | homepage = "https://github.com/codahale/nanostat" 9 | documentation = "https://docs.rs/nanostat/" 10 | keywords = ["statistics"] 11 | readme = "README.md" 12 | include = ["benches/**/*.rs", "src/**/*.rs", "tests/**/*.rs", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 13 | default-run = "nanostat" 14 | 15 | [workspace] 16 | members = ["xtask"] 17 | 18 | [features] 19 | default = [] 20 | cli = ["clap", "plotlib"] 21 | 22 | [dependencies] 23 | clap = { version = "4.1.1", optional = true, features = ["derive"] } 24 | plotlib = { version = "0.5.1", optional = true } 25 | statrs = "0.16.0" 26 | 27 | [dev-dependencies] 28 | approx = "0.5.1" 29 | criterion = { version = "0.4.0", features = ["html_reports"] } 30 | 31 | [[bench]] 32 | name = "benches" 33 | harness = false 34 | 35 | [[bin]] 36 | name = "nanostat" 37 | required-features = ["cli"] 38 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Coda Hale 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanostat 2 | 3 | Like ministat, but smaller? 4 | 5 | A Rust library and CLI tool for evaluating whether two or more sets of measurements are statistically different. It does 6 | this by performing a *Welch's t-test* at a particular confidence level, making it suitable for small sets of 7 | measurements (e.g., multiple runs of a benchmark). It's inspired largely by FreeBSD's `ministat` (written by 8 | Poul-Henning Kamp). 9 | 10 | ``` 11 | $ nanostat examples/iguana examples/leopard examples/chameleon 12 | examples/leopard: 13 | Difference at 95% confidence! 14 | 643.50 > 300.00 ± 293.97, p = .026 15 | examples/chameleon: 16 | No difference at 95% confidence. 17 | ``` 18 | 19 | ## Install 20 | 21 | ``` 22 | cargo install nanostat --features cli 23 | ``` 24 | 25 | ## License 26 | 27 | Copyright © 2021 Coda Hale 28 | 29 | Distributed under the Apache License 2.0 or the MIT license. 30 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use nanostat::Summary; 4 | 5 | fn summarize(c: &mut Criterion) { 6 | let v = vec![0.0; 1000]; 7 | c.bench_function("summarize", move |b| b.iter(|| v.iter().collect::())); 8 | } 9 | 10 | fn compare(c: &mut Criterion) { 11 | let s1: Summary = vec![0.0; 10].iter().collect(); 12 | let s2: Summary = vec![0.1; 10].iter().collect(); 13 | 14 | c.bench_function("compare", move |b| b.iter(|| s1.compare(&s2, 98.0))); 15 | } 16 | 17 | criterion_group!(benches, summarize, compare); 18 | criterion_main!(benches); 19 | -------------------------------------------------------------------------------- /examples/chameleon: -------------------------------------------------------------------------------- 1 | 150 2 | 400 3 | 720 4 | 500 5 | 930 6 | -------------------------------------------------------------------------------- /examples/iguana: -------------------------------------------------------------------------------- 1 | 50 2 | 200 3 | 150 4 | 400 5 | 750 6 | 400 7 | 150 8 | -------------------------------------------------------------------------------- /examples/leopard: -------------------------------------------------------------------------------- 1 | 353 2 | 574 3 | 495 4 | 1057 5 | 664 6 | 718 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Fuchsia Format Style 2 | # last reviewed: Jan 29, 2019 3 | 4 | # Fuchsia uses 2018 edition only 5 | edition = "2018" 6 | 7 | # The "Default" setting has a heuristic which splits lines too aggresively. 8 | # We are willing to revisit this setting in future versions of rustfmt. 9 | # Bugs: 10 | # * https://github.com/rust-lang/rustfmt/issues/3119 11 | # * https://github.com/rust-lang/rustfmt/issues/3120 12 | use_small_heuristics = "Max" 13 | 14 | # Prevent carriage returns 15 | newline_style = "Unix" 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! nanostat compares data sets using Welch's t-test at various levels of confidence. 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn( 5 | missing_docs, 6 | rust_2018_idioms, 7 | trivial_casts, 8 | unused_lifetimes, 9 | unused_qualifications, 10 | missing_copy_implementations, 11 | missing_debug_implementations, 12 | clippy::cognitive_complexity, 13 | clippy::missing_const_for_fn, 14 | clippy::needless_borrow 15 | )] 16 | 17 | use std::iter::FromIterator; 18 | 19 | use statrs::distribution::{ContinuousCDF, Normal, StudentsT}; 20 | 21 | /// The statistical difference between two [Summary] instances. 22 | #[derive(Copy, Clone, Debug)] 23 | pub struct Difference { 24 | /// The absolute difference between the samples' means. 25 | pub effect: f64, 26 | 27 | /// The difference in means between the two samples, normalized for variance. Technically, this 28 | /// is Cohen's d. 29 | pub effect_size: f64, 30 | 31 | /// The minimum allowed effect at the given confidence level. 32 | pub critical_value: f64, 33 | 34 | /// The p-value for the test: the probability that accepting the results of this test will be a 35 | /// Type 1 error, in which the null hypothesis (i.e. there is no difference between the means of 36 | /// the two samples) will be rejected when it is in fact true. 37 | pub p_value: f64, 38 | 39 | /// The significance level of the test. It is the maximum allowed value of the p-value. 40 | pub alpha: f64, 41 | 42 | /// The probability of a Type 2 error: the probability that the null hypothesis will be retained 43 | /// despite it not being true. 44 | pub beta: f64, 45 | } 46 | 47 | impl Difference { 48 | /// Whether or not the difference is statistically significant. 49 | #[must_use] 50 | pub fn is_significant(&self) -> bool { 51 | self.effect > self.critical_value 52 | } 53 | } 54 | 55 | /// A statistical summary of a normally distributed data set. 56 | /// 57 | /// Created from an iterable of `f64`s: 58 | /// 59 | /// ``` 60 | /// let summary: nanostat::Summary = vec![0.1, 0.45, 0.42].iter().collect(); 61 | /// ``` 62 | #[derive(Copy, Clone, Debug)] 63 | pub struct Summary { 64 | /// The number of measurements in the set. 65 | pub n: f64, 66 | /// The arithmetic mean of the measurements. 67 | pub mean: f64, 68 | /// The sample variance of the data set. 69 | pub variance: f64, 70 | } 71 | 72 | impl<'a> FromIterator<&'a f64> for Summary { 73 | fn from_iter>(iter: T) -> Self { 74 | // Welford's one-pass algorithm for corrected variance 75 | let (mut mean, mut s, mut n) = (0.0, 0.0, 0.0); 76 | for x in iter { 77 | n += 1.0; 78 | let delta = x - mean; 79 | mean += delta / n; 80 | s += delta * (x - mean); 81 | } 82 | let variance = s / (n - 1.0); // Bessel's correction 83 | Summary { n, mean, variance } 84 | } 85 | } 86 | 87 | impl Summary { 88 | /// The standard deviation of the sample. 89 | #[must_use] 90 | pub fn std_dev(&self) -> f64 { 91 | self.variance.sqrt() 92 | } 93 | 94 | /// The standard error of the sample. 95 | #[must_use] 96 | pub fn std_err(&self) -> f64 { 97 | self.std_dev() / self.n.sqrt() 98 | } 99 | 100 | /// Calculate the statistical difference between the two summaries using a two-tailed Welch's 101 | /// t-test. The confidence level must be in the range `(0, 100)`. 102 | #[must_use] 103 | pub fn compare(&self, other: &Summary, confidence: f64) -> Difference { 104 | assert!(0.0 < confidence && confidence < 100.0, "confidence must be (0,100)"); 105 | 106 | let (a, b) = (self, other); 107 | 108 | // Calculate the significance level. 109 | let alpha = 1.0 - (confidence / 100.0); 110 | 111 | // Calculate the degrees of freedom. 112 | let nu = (a.variance / a.n + b.variance / b.n).powf(2.0) 113 | / ((a.variance).powf(2.0) / ((a.n).powf(2.0) * (a.n - 1.0)) 114 | + (b.variance).powf(2.0) / ((b.n).powf(2.0) * (b.n - 1.0))); 115 | 116 | // Create a Student's T distribution with location of 0, a scale of 1, and the same number 117 | // of degrees of freedom as in the test. 118 | let dist_st = StudentsT::new(0.0, 1.0, nu).unwrap(); 119 | 120 | // Calculate the hypothetical two-tailed t-value for the given significance level. 121 | let t_hyp = dist_st.inverse_cdf(1.0 - (alpha / TAILS)); 122 | 123 | // Calculate the absolute difference between the means of the two samples. 124 | let effect = (a.mean - b.mean).abs(); 125 | 126 | // Calculate the standard error. 127 | let std_err = (a.variance / a.n + b.variance / b.n).sqrt(); 128 | 129 | // Calculate the experimental t-value. 130 | let t_exp = effect / std_err; 131 | 132 | // Calculate the p-value given the experimental t-value. 133 | let p_value = dist_st.cdf(-t_exp) * TAILS; 134 | 135 | // Calculate the critical value. 136 | let critical_value = t_hyp * std_err; 137 | 138 | // Calculate the standard deviation using mean variance. 139 | let std_dev = ((a.variance + b.variance) / 2.0).sqrt(); 140 | 141 | // Calculate Cohen's d for the effect size. 142 | let effect_size = effect / std_dev; 143 | 144 | // Calculate the statistical power. 145 | let z = effect / (std_dev * (1.0 / a.n + 1.0 / b.n).sqrt()); 146 | let dist_norm = Normal::new(0.0, 1.0).unwrap(); 147 | let za = dist_norm.inverse_cdf(1.0 - alpha / TAILS); 148 | let beta = dist_norm.cdf(z - za) - dist_norm.cdf(-z - za); 149 | 150 | Difference { effect, effect_size, critical_value, p_value, alpha, beta } 151 | } 152 | } 153 | 154 | /// The number of distribution tails used to determine significance. In this case, we always use a 155 | /// two-tailed test because our null hypothesis is that the samples are not different. 156 | const TAILS: f64 = 2.0; 157 | 158 | #[cfg(test)] 159 | mod test { 160 | use approx::assert_relative_eq; 161 | 162 | use super::*; 163 | 164 | #[test] 165 | fn summarize_odd() { 166 | let s: Summary = vec![1.0, 2.0, 3.0].iter().collect(); 167 | 168 | assert_relative_eq!(s.n, 3.0); 169 | assert_relative_eq!(s.mean, 2.0); 170 | assert_relative_eq!(s.variance, 1.0); 171 | } 172 | 173 | #[test] 174 | fn summarize_even() { 175 | let s: Summary = vec![1.0, 2.0, 3.0, 4.0].iter().collect(); 176 | 177 | assert_relative_eq!(s.n, 4.0); 178 | assert_relative_eq!(s.mean, 2.5); 179 | assert_relative_eq!(s.variance, 1.6666666666666667); 180 | } 181 | 182 | #[test] 183 | fn compare_similar_data() { 184 | let a: Summary = vec![1.0, 2.0, 3.0, 4.0].iter().collect(); 185 | let b: Summary = vec![1.0, 2.0, 3.0, 4.0].iter().collect(); 186 | let diff = a.compare(&b, 80.0); 187 | 188 | assert_relative_eq!(diff.effect, 0.0); 189 | assert_relative_eq!(diff.effect_size, 0.0); 190 | assert_relative_eq!(diff.critical_value, 1.3143111667913936); 191 | assert_relative_eq!(diff.p_value, 1.0); 192 | assert_relative_eq!(diff.alpha, 0.19999999999999996); 193 | assert_relative_eq!(diff.beta, 0.0); 194 | assert!(!diff.is_significant()); 195 | } 196 | 197 | #[test] 198 | fn compare_different_data() { 199 | let a: Summary = vec![1.0, 2.0, 3.0, 4.0].iter().collect(); 200 | let b: Summary = vec![10.0, 20.0, 30.0, 40.0].iter().collect(); 201 | let diff = a.compare(&b, 80.0); 202 | 203 | assert_relative_eq!(diff.effect, 22.5); 204 | assert_relative_eq!(diff.effect_size, 2.452519415855564); 205 | assert_relative_eq!(diff.critical_value, 10.568344341563591); 206 | assert_relative_eq!(diff.p_value, 0.03916791618893325); 207 | assert_relative_eq!(diff.alpha, 0.19999999999999996); 208 | assert_relative_eq!(diff.beta, 0.985621684277956); 209 | assert!(diff.is_significant()); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::{BufRead, BufReader}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use clap::{Parser, ValueHint}; 7 | use plotlib::page::Page; 8 | use plotlib::repr::BoxPlot; 9 | use plotlib::view::CategoricalView; 10 | 11 | use nanostat::Summary; 12 | 13 | /// Check for statistically valid differences between sets of measurements. 14 | #[derive(Debug, Parser)] 15 | struct Opt { 16 | /// The path to a file with per-line floating point values. 17 | #[clap(action, value_hint = ValueHint::FilePath)] 18 | control: PathBuf, 19 | 20 | /// The paths to one or more files with per-line floating point values. 21 | #[clap(action, value_hint = ValueHint::FilePath)] 22 | experiments: Vec, 23 | 24 | /// The statistical confidence required (0,100). 25 | #[clap(action, short = 'c', long, default_value = "95.0")] 26 | confidence: f64, 27 | 28 | /// Write an SVG box plot to the given path. 29 | #[clap(action, long, value_hint = ValueHint::FilePath, value_name = "PATH")] 30 | box_plot: Option, 31 | } 32 | 33 | fn main() -> Result<(), Box> { 34 | let opt: Opt = Opt::parse(); 35 | 36 | let mut plots = CategoricalView::new(); 37 | 38 | let (ctrl_data, ctrl) = read_file(&opt.control)?; 39 | plots = plots.add(BoxPlot::from_vec(ctrl_data).label(opt.control.to_string_lossy())); 40 | 41 | for path in opt.experiments { 42 | let (exp_data, exp) = read_file(&path)?; 43 | plots = plots.add(BoxPlot::from_vec(exp_data).label(path.to_string_lossy())); 44 | 45 | let diff = ctrl.compare(&exp, opt.confidence); 46 | 47 | println!("{}:", path.to_string_lossy()); 48 | if diff.is_significant() { 49 | let p = format!("{:.3}", diff.p_value); 50 | let p = p.trim_start_matches('0'); 51 | let op = if exp.mean < ctrl.mean { "<" } else { ">" }; 52 | 53 | println!("\tDifference at {}% confidence!", opt.confidence); 54 | println!( 55 | "\t\t{:.2} {} {:.2} ± {:.2}, p = {}", 56 | exp.mean, op, ctrl.mean, diff.critical_value, p, 57 | ); 58 | } else { 59 | println!("\tNo difference at {}% confidence.\n", opt.confidence); 60 | } 61 | } 62 | 63 | if let Some(path) = opt.box_plot { 64 | Page::single(&plots).save(&path)?; 65 | } 66 | 67 | Ok(()) 68 | } 69 | 70 | fn read_file(path: &Path) -> Result<(Vec, Summary), Box> { 71 | let mut values = vec![]; 72 | for l in BufReader::new(File::open(path)?).lines() { 73 | values.push(l?.parse()?); 74 | } 75 | let summary = values.iter().collect(); 76 | Ok((values, summary)) 77 | } 78 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.68" 8 | clap = { version = "4.1.1", features = ["derive"] } 9 | xshell = "0.2.3" 10 | 11 | [[bin]] 12 | name = "xtask" 13 | path = "src/xtask.rs" 14 | -------------------------------------------------------------------------------- /xtask/src/xtask.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use anyhow::Result; 5 | use clap::{Parser, Subcommand}; 6 | use xshell::{cmd, Shell}; 7 | 8 | #[derive(Debug, Parser)] 9 | struct XTask { 10 | #[clap(subcommand)] 11 | cmd: Option, 12 | } 13 | 14 | #[derive(Debug, Subcommand)] 15 | enum Command { 16 | /// Format, build, test, and lint. 17 | CI, 18 | } 19 | 20 | fn main() -> Result<()> { 21 | let _ = XTask::parse(); 22 | 23 | let sh = Shell::new()?; 24 | sh.change_dir(project_root()); 25 | 26 | cmd!(sh, "cargo fmt --check").run()?; 27 | cmd!(sh, "cargo build --all-targets --all-features").run()?; 28 | cmd!(sh, "cargo test --all-features").run()?; 29 | cmd!(sh, "cargo clippy --all-features --tests --benches").run()?; 30 | 31 | Ok(()) 32 | } 33 | 34 | fn project_root() -> PathBuf { 35 | Path::new( 36 | &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), 37 | ) 38 | .ancestors() 39 | .nth(1) 40 | .unwrap() 41 | .to_path_buf() 42 | } 43 | --------------------------------------------------------------------------------